diff --git a/packages/kilo-i18n/src/ar.ts b/packages/kilo-i18n/src/ar.ts index 4abab3098f2..f61e3330f82 100644 --- a/packages/kilo-i18n/src/ar.ts +++ b/packages/kilo-i18n/src/ar.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "المهارات", "marketplace.tab.mcpServers": "خوادم MCP", "marketplace.tab.modes": "الأوضاع", - "marketplace.search.skills": "البحث في المهارات...", "marketplace.category.all": "الكل", - "marketplace.empty.skills": "لم يتم العثور على مهارات", "marketplace.placeholder": "سيتم تنفيذه لاحقاً", "marketplace.card.installed": "مثبت", "marketplace.card.install": "تثبيت", "marketplace.card.remove": "إزالة", "marketplace.card.removeScope": "إزالة ({{scope}})", + "marketplace.card.showMore": "عرض المزيد", + "marketplace.card.showLess": "عرض أقل", "marketplace.install.title": "تثبيت {{name}}", "marketplace.install.scope": "النطاق", "marketplace.install.scope.project": "المشروع", @@ -43,4 +43,25 @@ export const dict = { "هل أنت متأكد أنك تريد إزالة هذا الـ {{type}}؟ سيؤدي هذا إلى إزالته من تكوين {{scope}} الخاص بك.", "marketplace.remove.cancel": "إلغاء", "marketplace.remove.confirm.button": "إزالة", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "بحث...", + "marketplace.filter.all": "جميع العناصر", + "marketplace.filter.notInstalled": "غير مثبت", + "marketplace.empty": "لم يتم العثور على عناصر", + "marketplace.badge.mcpServer": "خادم MCP", + "marketplace.badge.mode": "وضع", + "marketplace.card.by": "بواسطة {{author}}", + "marketplace.install.method": "طريقة التثبيت", + "marketplace.install.parameters": "المعلمات", + "marketplace.install.optional": "(اختياري)", + "marketplace.install.required": "{{name}} مطلوب", + "marketplace.scope.project": "مشروع", + "marketplace.scope.global": "عام", + "marketplace.remove.type.mcp": "خادم MCP", + "marketplace.remove.type.skill": "مهارة", + "marketplace.remove.type.mode": "وضع", + "marketplace.remove.failed": "فشلت إزالة {{name}}", + "marketplace.install": "تثبيت", + "marketplace.filter.installed": "مثبت", + "marketplace.error.dismiss": "تجاهل", } diff --git a/packages/kilo-i18n/src/br.ts b/packages/kilo-i18n/src/br.ts index cfa23e214d6..e5cdf8251d8 100644 --- a/packages/kilo-i18n/src/br.ts +++ b/packages/kilo-i18n/src/br.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "Skills", "marketplace.tab.mcpServers": "Servidores MCP", "marketplace.tab.modes": "Modos", - "marketplace.search.skills": "Buscar skills...", "marketplace.category.all": "Todos", - "marketplace.empty.skills": "Nenhuma skill encontrada", "marketplace.placeholder": "A ser implementado", "marketplace.card.installed": "Instalado", "marketplace.card.install": "Instalar", "marketplace.card.remove": "Remover", "marketplace.card.removeScope": "Remover ({{scope}})", + "marketplace.card.showMore": "Mostrar mais", + "marketplace.card.showLess": "Mostrar menos", "marketplace.install.title": "Instalar {{name}}", "marketplace.install.scope": "Escopo", "marketplace.install.scope.project": "Projeto", @@ -43,4 +43,25 @@ export const dict = { "Tem certeza que deseja remover este {{type}}? Isso o removerá da sua configuração {{scope}}.", "marketplace.remove.cancel": "Cancelar", "marketplace.remove.confirm.button": "Remover", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Pesquisar...", + "marketplace.filter.all": "Todos os Itens", + "marketplace.filter.notInstalled": "Não Instalado", + "marketplace.empty": "Nenhum item encontrado", + "marketplace.badge.mcpServer": "Servidor MCP", + "marketplace.badge.mode": "Modo", + "marketplace.card.by": "por {{author}}", + "marketplace.install.method": "Método de Instalação", + "marketplace.install.parameters": "Parâmetros", + "marketplace.install.optional": "(opcional)", + "marketplace.install.required": "{{name}} é obrigatório", + "marketplace.scope.project": "projeto", + "marketplace.scope.global": "global", + "marketplace.remove.type.mcp": "servidor MCP", + "marketplace.remove.type.skill": "habilidade", + "marketplace.remove.type.mode": "modo", + "marketplace.remove.failed": "Falha ao remover {{name}}", + "marketplace.install": "Instalar", + "marketplace.filter.installed": "Instalado", + "marketplace.error.dismiss": "Dispensar", } diff --git a/packages/kilo-i18n/src/bs.ts b/packages/kilo-i18n/src/bs.ts index aecc7423546..ddd8c5cffad 100644 --- a/packages/kilo-i18n/src/bs.ts +++ b/packages/kilo-i18n/src/bs.ts @@ -24,14 +24,14 @@ export const dict = { "marketplace.tab.skills": "Vještine", "marketplace.tab.mcpServers": "MCP Serveri", "marketplace.tab.modes": "Modovi", - "marketplace.search.skills": "Pretraži vještine...", "marketplace.category.all": "Sve", - "marketplace.empty.skills": "Nema pronađenih vještina", "marketplace.placeholder": "Biće implementirano", "marketplace.card.installed": "Instalirano", "marketplace.card.install": "Instaliraj", "marketplace.card.remove": "Ukloni", "marketplace.card.removeScope": "Ukloni ({{scope}})", + "marketplace.card.showMore": "Prikaži više", + "marketplace.card.showLess": "Prikaži manje", "marketplace.install.title": "Instaliraj {{name}}", "marketplace.install.scope": "Opseg", "marketplace.install.scope.project": "Projekat", @@ -48,4 +48,25 @@ export const dict = { "Jeste li sigurni da želite ukloniti ovaj {{type}}? Ovo će ga ukloniti iz vaše {{scope}} konfiguracije.", "marketplace.remove.cancel": "Otkaži", "marketplace.remove.confirm.button": "Ukloni", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Pretraži...", + "marketplace.filter.all": "Sve stavke", + "marketplace.filter.notInstalled": "Nije instalirano", + "marketplace.empty": "Nema pronađenih stavki", + "marketplace.badge.mcpServer": "MCP Server", + "marketplace.badge.mode": "Režim", + "marketplace.card.by": "od {{author}}", + "marketplace.install.method": "Metoda instalacije", + "marketplace.install.parameters": "Parametri", + "marketplace.install.optional": "(opciono)", + "marketplace.install.required": "{{name}} je obavezno", + "marketplace.scope.project": "projekat", + "marketplace.scope.global": "globalno", + "marketplace.remove.type.mcp": "MCP server", + "marketplace.remove.type.skill": "vještina", + "marketplace.remove.type.mode": "režim", + "marketplace.remove.failed": "Uklanjanje {{name}} nije uspjelo", + "marketplace.install": "Instaliraj", + "marketplace.filter.installed": "Instalirano", + "marketplace.error.dismiss": "Odbaci", } diff --git a/packages/kilo-i18n/src/da.ts b/packages/kilo-i18n/src/da.ts index fb5541bdaeb..5de93df58a1 100644 --- a/packages/kilo-i18n/src/da.ts +++ b/packages/kilo-i18n/src/da.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "Skills", "marketplace.tab.mcpServers": "MCP-servere", "marketplace.tab.modes": "Tilstande", - "marketplace.search.skills": "Søg efter skills...", "marketplace.category.all": "Alle", - "marketplace.empty.skills": "Ingen skills fundet", "marketplace.placeholder": "Skal implementeres", "marketplace.card.installed": "Installeret", "marketplace.card.install": "Installer", "marketplace.card.remove": "Fjern", "marketplace.card.removeScope": "Fjern ({{scope}})", + "marketplace.card.showMore": "Vis mere", + "marketplace.card.showLess": "Vis mindre", "marketplace.install.title": "Installer {{name}}", "marketplace.install.scope": "Omfang", "marketplace.install.scope.project": "Projekt", @@ -43,4 +43,25 @@ export const dict = { "Er du sikker på, at du vil fjerne denne {{type}}? Dette vil fjerne den fra din {{scope}} konfiguration.", "marketplace.remove.cancel": "Annuller", "marketplace.remove.confirm.button": "Fjern", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Søg...", + "marketplace.filter.all": "Alle elementer", + "marketplace.filter.notInstalled": "Ikke installeret", + "marketplace.empty": "Ingen elementer fundet", + "marketplace.badge.mcpServer": "MCP-server", + "marketplace.badge.mode": "Tilstand", + "marketplace.card.by": "af {{author}}", + "marketplace.install.method": "Installationsmetode", + "marketplace.install.parameters": "Parametre", + "marketplace.install.optional": "(valgfrit)", + "marketplace.install.required": "{{name}} er påkrævet", + "marketplace.scope.project": "projekt", + "marketplace.scope.global": "global", + "marketplace.remove.type.mcp": "MCP-server", + "marketplace.remove.type.skill": "færdighed", + "marketplace.remove.type.mode": "tilstand", + "marketplace.remove.failed": "Kunne ikke fjerne {{name}}", + "marketplace.install": "Installer", + "marketplace.filter.installed": "Installeret", + "marketplace.error.dismiss": "Afvis", } diff --git a/packages/kilo-i18n/src/de.ts b/packages/kilo-i18n/src/de.ts index bd34499ade6..7952050b42d 100644 --- a/packages/kilo-i18n/src/de.ts +++ b/packages/kilo-i18n/src/de.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "Skills", "marketplace.tab.mcpServers": "MCP-Server", "marketplace.tab.modes": "Modi", - "marketplace.search.skills": "Skills suchen...", "marketplace.category.all": "Alle", - "marketplace.empty.skills": "Keine Skills gefunden", "marketplace.placeholder": "Noch nicht implementiert", "marketplace.card.installed": "Installiert", "marketplace.card.install": "Installieren", "marketplace.card.remove": "Entfernen", "marketplace.card.removeScope": "Entfernen ({{scope}})", + "marketplace.card.showMore": "Mehr anzeigen", + "marketplace.card.showLess": "Weniger anzeigen", "marketplace.install.title": "{{name}} installieren", "marketplace.install.scope": "Bereich", "marketplace.install.scope.project": "Projekt", @@ -43,4 +43,25 @@ export const dict = { "Sind Sie sicher, dass Sie diese {{type}} entfernen möchten? Dadurch wird sie aus Ihrer {{scope}}-Konfiguration entfernt.", "marketplace.remove.cancel": "Abbrechen", "marketplace.remove.confirm.button": "Entfernen", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Suchen...", + "marketplace.filter.all": "Alle Elemente", + "marketplace.filter.notInstalled": "Nicht installiert", + "marketplace.empty": "Keine Elemente gefunden", + "marketplace.badge.mcpServer": "MCP-Server", + "marketplace.badge.mode": "Modus", + "marketplace.card.by": "von {{author}}", + "marketplace.install.method": "Installationsmethode", + "marketplace.install.parameters": "Parameter", + "marketplace.install.optional": "(optional)", + "marketplace.install.required": "{{name}} ist erforderlich", + "marketplace.scope.project": "Projekt", + "marketplace.scope.global": "Global", + "marketplace.remove.type.mcp": "MCP-Server", + "marketplace.remove.type.skill": "Skill", + "marketplace.remove.type.mode": "Modus", + "marketplace.remove.failed": "Fehler beim Entfernen von {{name}}", + "marketplace.install": "Installieren", + "marketplace.filter.installed": "Installiert", + "marketplace.error.dismiss": "Verwerfen", } diff --git a/packages/kilo-i18n/src/en.ts b/packages/kilo-i18n/src/en.ts index 5cccd33c5c2..a2d0c30011d 100644 --- a/packages/kilo-i18n/src/en.ts +++ b/packages/kilo-i18n/src/en.ts @@ -21,14 +21,14 @@ export const dict = { "marketplace.tab.skills": "Skills", "marketplace.tab.mcpServers": "MCP Servers", "marketplace.tab.modes": "Modes", - "marketplace.search.skills": "Search skills...", "marketplace.category.all": "All", - "marketplace.empty.skills": "No skills found", "marketplace.placeholder": "To be implemented", "marketplace.card.installed": "Installed", "marketplace.card.install": "Install", "marketplace.card.remove": "Remove", "marketplace.card.removeScope": "Remove ({{scope}})", + "marketplace.card.showMore": "Show more", + "marketplace.card.showLess": "Show less", "marketplace.install.title": "Install {{name}}", "marketplace.install.scope": "Scope", "marketplace.install.scope.project": "Project", @@ -45,4 +45,25 @@ export const dict = { "Are you sure you want to remove this {{type}}? This will remove it from your {{scope}} configuration.", "marketplace.remove.cancel": "Cancel", "marketplace.remove.confirm.button": "Remove", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Search...", + "marketplace.filter.all": "All Items", + "marketplace.filter.notInstalled": "Not Installed", + "marketplace.empty": "No items found", + "marketplace.badge.mcpServer": "MCP Server", + "marketplace.badge.mode": "Mode", + "marketplace.card.by": "by {{author}}", + "marketplace.install.method": "Installation Method", + "marketplace.install.parameters": "Parameters", + "marketplace.install.optional": "(optional)", + "marketplace.install.required": "{{name}} is required", + "marketplace.scope.project": "project", + "marketplace.scope.global": "global", + "marketplace.remove.type.mcp": "MCP server", + "marketplace.remove.type.skill": "skill", + "marketplace.remove.type.mode": "mode", + "marketplace.remove.failed": "Failed to remove {{name}}", + "marketplace.install": "Install", + "marketplace.filter.installed": "Installed", + "marketplace.error.dismiss": "Dismiss", } diff --git a/packages/kilo-i18n/src/es.ts b/packages/kilo-i18n/src/es.ts index cdedb7864b8..bede5eb3727 100644 --- a/packages/kilo-i18n/src/es.ts +++ b/packages/kilo-i18n/src/es.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "Skills", "marketplace.tab.mcpServers": "Servidores MCP", "marketplace.tab.modes": "Modos", - "marketplace.search.skills": "Buscar skills...", "marketplace.category.all": "Todos", - "marketplace.empty.skills": "No se encontraron skills", "marketplace.placeholder": "Por implementar", "marketplace.card.installed": "Instalado", "marketplace.card.install": "Instalar", "marketplace.card.remove": "Eliminar", "marketplace.card.removeScope": "Eliminar ({{scope}})", + "marketplace.card.showMore": "Mostrar más", + "marketplace.card.showLess": "Mostrar menos", "marketplace.install.title": "Instalar {{name}}", "marketplace.install.scope": "Alcance", "marketplace.install.scope.project": "Proyecto", @@ -43,4 +43,25 @@ export const dict = { "¿Estás seguro de que deseas eliminar este {{type}}? Esto lo eliminará de tu configuración {{scope}}.", "marketplace.remove.cancel": "Cancelar", "marketplace.remove.confirm.button": "Eliminar", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Buscar...", + "marketplace.filter.all": "Todos los elementos", + "marketplace.filter.notInstalled": "No instalado", + "marketplace.empty": "No se encontraron elementos", + "marketplace.badge.mcpServer": "Servidor MCP", + "marketplace.badge.mode": "Modo", + "marketplace.card.by": "por {{author}}", + "marketplace.install.method": "Método de instalación", + "marketplace.install.parameters": "Parámetros", + "marketplace.install.optional": "(opcional)", + "marketplace.install.required": "{{name}} es obligatorio", + "marketplace.scope.project": "proyecto", + "marketplace.scope.global": "global", + "marketplace.remove.type.mcp": "servidor MCP", + "marketplace.remove.type.skill": "habilidad", + "marketplace.remove.type.mode": "modo", + "marketplace.remove.failed": "Error al eliminar {{name}}", + "marketplace.install": "Instalar", + "marketplace.filter.installed": "Instalado", + "marketplace.error.dismiss": "Descartar", } diff --git a/packages/kilo-i18n/src/fr.ts b/packages/kilo-i18n/src/fr.ts index e741db9adc7..786758db4fd 100644 --- a/packages/kilo-i18n/src/fr.ts +++ b/packages/kilo-i18n/src/fr.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "Skills", "marketplace.tab.mcpServers": "Serveurs MCP", "marketplace.tab.modes": "Modes", - "marketplace.search.skills": "Rechercher des skills...", "marketplace.category.all": "Tout", - "marketplace.empty.skills": "Aucune skill trouvée", "marketplace.placeholder": "À implémenter", "marketplace.card.installed": "Installé", "marketplace.card.install": "Installer", "marketplace.card.remove": "Supprimer", "marketplace.card.removeScope": "Supprimer ({{scope}})", + "marketplace.card.showMore": "Afficher plus", + "marketplace.card.showLess": "Afficher moins", "marketplace.install.title": "Installer {{name}}", "marketplace.install.scope": "Portée", "marketplace.install.scope.project": "Projet", @@ -43,4 +43,25 @@ export const dict = { "Êtes-vous sûr de vouloir supprimer ce {{type}} ? Cela le supprimera de votre configuration {{scope}}.", "marketplace.remove.cancel": "Annuler", "marketplace.remove.confirm.button": "Supprimer", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Rechercher...", + "marketplace.filter.all": "Tous les éléments", + "marketplace.filter.notInstalled": "Non installé", + "marketplace.empty": "Aucun élément trouvé", + "marketplace.badge.mcpServer": "Serveur MCP", + "marketplace.badge.mode": "Mode", + "marketplace.card.by": "par {{author}}", + "marketplace.install.method": "Méthode d'installation", + "marketplace.install.parameters": "Paramètres", + "marketplace.install.optional": "(facultatif)", + "marketplace.install.required": "{{name}} est requis", + "marketplace.scope.project": "projet", + "marketplace.scope.global": "global", + "marketplace.remove.type.mcp": "serveur MCP", + "marketplace.remove.type.skill": "compétence", + "marketplace.remove.type.mode": "mode", + "marketplace.remove.failed": "Échec de la suppression de {{name}}", + "marketplace.install": "Installer", + "marketplace.filter.installed": "Installé", + "marketplace.error.dismiss": "Ignorer", } diff --git a/packages/kilo-i18n/src/ja.ts b/packages/kilo-i18n/src/ja.ts index 460697114ab..2f2658aaf06 100644 --- a/packages/kilo-i18n/src/ja.ts +++ b/packages/kilo-i18n/src/ja.ts @@ -18,14 +18,14 @@ export const dict = { "marketplace.tab.skills": "スキル", "marketplace.tab.mcpServers": "MCPサーバー", "marketplace.tab.modes": "モード", - "marketplace.search.skills": "スキルを検索...", "marketplace.category.all": "すべて", - "marketplace.empty.skills": "スキルが見つかりません", "marketplace.placeholder": "未実装", "marketplace.card.installed": "インストール済み", "marketplace.card.install": "インストール", "marketplace.card.remove": "削除", "marketplace.card.removeScope": "削除 ({{scope}})", + "marketplace.card.showMore": "もっと見る", + "marketplace.card.showLess": "折りたたむ", "marketplace.install.title": "{{name}} のインストール", "marketplace.install.scope": "スコープ", "marketplace.install.scope.project": "プロジェクト", @@ -41,4 +41,25 @@ export const dict = { "marketplace.remove.confirm": "この{{type}}を削除してもよろしいですか? これにより、{{scope}}設定から削除されます。", "marketplace.remove.cancel": "キャンセル", "marketplace.remove.confirm.button": "削除", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "検索...", + "marketplace.filter.all": "すべてのアイテム", + "marketplace.filter.notInstalled": "未インストール", + "marketplace.empty": "アイテムが見つかりません", + "marketplace.badge.mcpServer": "MCPサーバー", + "marketplace.badge.mode": "モード", + "marketplace.card.by": "作成者: {{author}}", + "marketplace.install.method": "インストール方法", + "marketplace.install.parameters": "パラメーター", + "marketplace.install.optional": "(任意)", + "marketplace.install.required": "{{name}} は必須です", + "marketplace.scope.project": "プロジェクト", + "marketplace.scope.global": "グローバル", + "marketplace.remove.type.mcp": "MCPサーバー", + "marketplace.remove.type.skill": "スキル", + "marketplace.remove.type.mode": "モード", + "marketplace.remove.failed": "{{name}} の削除に失敗しました", + "marketplace.install": "インストール", + "marketplace.filter.installed": "インストール済み", + "marketplace.error.dismiss": "閉じる", } diff --git a/packages/kilo-i18n/src/ko.ts b/packages/kilo-i18n/src/ko.ts index 1d2d11e4443..e3bc71042b6 100644 --- a/packages/kilo-i18n/src/ko.ts +++ b/packages/kilo-i18n/src/ko.ts @@ -18,14 +18,14 @@ export const dict = { "marketplace.tab.skills": "스킬", "marketplace.tab.mcpServers": "MCP 서버", "marketplace.tab.modes": "모드", - "marketplace.search.skills": "스킬 검색...", "marketplace.category.all": "전체", - "marketplace.empty.skills": "스킬을 찾을 수 없습니다", "marketplace.placeholder": "구현 예정", "marketplace.card.installed": "설치됨", "marketplace.card.install": "설치", "marketplace.card.remove": "제거", "marketplace.card.removeScope": "제거 ({{scope}})", + "marketplace.card.showMore": "더 보기", + "marketplace.card.showLess": "접기", "marketplace.install.title": "{{name}} 설치", "marketplace.install.scope": "범위", "marketplace.install.scope.project": "프로젝트", @@ -41,4 +41,25 @@ export const dict = { "marketplace.remove.confirm": "이 {{type}}을(를) 제거하시겠습니까? {{scope}} 구성에서 제거됩니다.", "marketplace.remove.cancel": "취소", "marketplace.remove.confirm.button": "제거", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "검색...", + "marketplace.filter.all": "모든 항목", + "marketplace.filter.notInstalled": "설치되지 않음", + "marketplace.empty": "항목을 찾을 수 없음", + "marketplace.badge.mcpServer": "MCP 서버", + "marketplace.badge.mode": "모드", + "marketplace.card.by": "제작: {{author}}", + "marketplace.install.method": "설치 방법", + "marketplace.install.parameters": "매개변수", + "marketplace.install.optional": "(선택 사항)", + "marketplace.install.required": "{{name}}이(가) 필요합니다", + "marketplace.scope.project": "프로젝트", + "marketplace.scope.global": "글로벌", + "marketplace.remove.type.mcp": "MCP 서버", + "marketplace.remove.type.skill": "스킬", + "marketplace.remove.type.mode": "모드", + "marketplace.remove.failed": "{{name}} 제거 실패", + "marketplace.install": "설치", + "marketplace.filter.installed": "설치됨", + "marketplace.error.dismiss": "닫기", } diff --git a/packages/kilo-i18n/src/no.ts b/packages/kilo-i18n/src/no.ts index 70e5f3a3e0c..6e1687e1a67 100644 --- a/packages/kilo-i18n/src/no.ts +++ b/packages/kilo-i18n/src/no.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "Skills", "marketplace.tab.mcpServers": "MCP-servere", "marketplace.tab.modes": "Moduser", - "marketplace.search.skills": "Søk etter skills...", "marketplace.category.all": "Alle", - "marketplace.empty.skills": "Ingen skills funnet", "marketplace.placeholder": "Skal implementeres", "marketplace.card.installed": "Installert", "marketplace.card.install": "Installer", "marketplace.card.remove": "Fjern", "marketplace.card.removeScope": "Fjern ({{scope}})", + "marketplace.card.showMore": "Vis mer", + "marketplace.card.showLess": "Vis mindre", "marketplace.install.title": "Installer {{name}}", "marketplace.install.scope": "Omfang", "marketplace.install.scope.project": "Prosjekt", @@ -43,4 +43,25 @@ export const dict = { "Er du sikker på at du vil fjerne denne {{type}}? Dette vil fjerne den fra din {{scope}}-konfigurasjon.", "marketplace.remove.cancel": "Avbryt", "marketplace.remove.confirm.button": "Fjern", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Søk...", + "marketplace.filter.all": "Alle elementer", + "marketplace.filter.notInstalled": "Ikke installert", + "marketplace.empty": "Ingen elementer funnet", + "marketplace.badge.mcpServer": "MCP-server", + "marketplace.badge.mode": "Modus", + "marketplace.card.by": "av {{author}}", + "marketplace.install.method": "Installasjonsmetode", + "marketplace.install.parameters": "Parametere", + "marketplace.install.optional": "(valgfritt)", + "marketplace.install.required": "{{name}} er påkrevd", + "marketplace.scope.project": "prosjekt", + "marketplace.scope.global": "global", + "marketplace.remove.type.mcp": "MCP-server", + "marketplace.remove.type.skill": "ferdighet", + "marketplace.remove.type.mode": "modus", + "marketplace.remove.failed": "Kunne ikke fjerne {{name}}", + "marketplace.install": "Installer", + "marketplace.filter.installed": "Installert", + "marketplace.error.dismiss": "Avvis", } diff --git a/packages/kilo-i18n/src/pl.ts b/packages/kilo-i18n/src/pl.ts index b4b7f0685a1..0951308b723 100644 --- a/packages/kilo-i18n/src/pl.ts +++ b/packages/kilo-i18n/src/pl.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "Skills", "marketplace.tab.mcpServers": "Serwery MCP", "marketplace.tab.modes": "Tryby", - "marketplace.search.skills": "Szukaj skills...", "marketplace.category.all": "Wszystkie", - "marketplace.empty.skills": "Nie znaleziono skills", "marketplace.placeholder": "Do zaimplementowania", "marketplace.card.installed": "Zainstalowano", "marketplace.card.install": "Zainstaluj", "marketplace.card.remove": "Usuń", "marketplace.card.removeScope": "Usuń ({{scope}})", + "marketplace.card.showMore": "Pokaż więcej", + "marketplace.card.showLess": "Pokaż mniej", "marketplace.install.title": "Instalacja: {{name}}", "marketplace.install.scope": "Zakres", "marketplace.install.scope.project": "Projekt", @@ -43,4 +43,25 @@ export const dict = { "Czy na pewno chcesz usunąć ten {{type}}? Spowoduje to usunięcie z konfiguracji: {{scope}}.", "marketplace.remove.cancel": "Anuluj", "marketplace.remove.confirm.button": "Usuń", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Szukaj...", + "marketplace.filter.all": "Wszystkie elementy", + "marketplace.filter.notInstalled": "Nie zainstalowano", + "marketplace.empty": "Nie znaleziono elementów", + "marketplace.badge.mcpServer": "Serwer MCP", + "marketplace.badge.mode": "Tryb", + "marketplace.card.by": "autor: {{author}}", + "marketplace.install.method": "Metoda instalacji", + "marketplace.install.parameters": "Parametry", + "marketplace.install.optional": "(opcjonalne)", + "marketplace.install.required": "{{name}} jest wymagane", + "marketplace.scope.project": "projekt", + "marketplace.scope.global": "globalny", + "marketplace.remove.type.mcp": "serwer MCP", + "marketplace.remove.type.skill": "umiejętność", + "marketplace.remove.type.mode": "tryb", + "marketplace.remove.failed": "Nie udało się usunąć {{name}}", + "marketplace.install": "Zainstaluj", + "marketplace.filter.installed": "Zainstalowano", + "marketplace.error.dismiss": "Odrzuć", } diff --git a/packages/kilo-i18n/src/ru.ts b/packages/kilo-i18n/src/ru.ts index 581b6925967..11fc5db56e2 100644 --- a/packages/kilo-i18n/src/ru.ts +++ b/packages/kilo-i18n/src/ru.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "Навыки", "marketplace.tab.mcpServers": "MCP-серверы", "marketplace.tab.modes": "Режимы", - "marketplace.search.skills": "Поиск навыков...", "marketplace.category.all": "Все", - "marketplace.empty.skills": "Навыки не найдены", "marketplace.placeholder": "Будет реализовано", "marketplace.card.installed": "Установлено", "marketplace.card.install": "Установить", "marketplace.card.remove": "Удалить", "marketplace.card.removeScope": "Удалить ({{scope}})", + "marketplace.card.showMore": "Показать больше", + "marketplace.card.showLess": "Показать меньше", "marketplace.install.title": "Установка {{name}}", "marketplace.install.scope": "Область", "marketplace.install.scope.project": "Проект", @@ -43,4 +43,25 @@ export const dict = { "Вы уверены, что хотите удалить этот {{type}}? Это удалит его из вашей конфигурации ({{scope}}).", "marketplace.remove.cancel": "Отмена", "marketplace.remove.confirm.button": "Удалить", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Поиск...", + "marketplace.filter.all": "Все элементы", + "marketplace.filter.notInstalled": "Не установлено", + "marketplace.empty": "Элементы не найдены", + "marketplace.badge.mcpServer": "MCP-сервер", + "marketplace.badge.mode": "Режим", + "marketplace.card.by": "от {{author}}", + "marketplace.install.method": "Способ установки", + "marketplace.install.parameters": "Параметры", + "marketplace.install.optional": "(необязательно)", + "marketplace.install.required": "Требуется {{name}}", + "marketplace.scope.project": "проект", + "marketplace.scope.global": "глобально", + "marketplace.remove.type.mcp": "MCP-сервер", + "marketplace.remove.type.skill": "навык", + "marketplace.remove.type.mode": "режим", + "marketplace.remove.failed": "Не удалось удалить {{name}}", + "marketplace.install": "Установить", + "marketplace.filter.installed": "Установлено", + "marketplace.error.dismiss": "Закрыть", } diff --git a/packages/kilo-i18n/src/th.ts b/packages/kilo-i18n/src/th.ts index 5fe5514aab0..65b1127e7df 100644 --- a/packages/kilo-i18n/src/th.ts +++ b/packages/kilo-i18n/src/th.ts @@ -19,14 +19,14 @@ export const dict = { "marketplace.tab.skills": "ทักษะ", "marketplace.tab.mcpServers": "เซิร์ฟเวอร์ MCP", "marketplace.tab.modes": "โหมด", - "marketplace.search.skills": "ค้นหาทักษะ...", "marketplace.category.all": "ทั้งหมด", - "marketplace.empty.skills": "ไม่พบทักษะ", "marketplace.placeholder": "ยังไม่ได้ดำเนินการ", "marketplace.card.installed": "ติดตั้งแล้ว", "marketplace.card.install": "ติดตั้ง", "marketplace.card.remove": "ลบ", "marketplace.card.removeScope": "ลบ ({{scope}})", + "marketplace.card.showMore": "แสดงเพิ่มเติม", + "marketplace.card.showLess": "แสดงน้อยลง", "marketplace.install.title": "ติดตั้ง {{name}}", "marketplace.install.scope": "ขอบเขต", "marketplace.install.scope.project": "โปรเจกต์", @@ -43,4 +43,25 @@ export const dict = { "คุณแน่ใจหรือไม่ว่าต้องการลบ {{type}} นี้? สิ่งนี้จะลบออกจากการกำหนดค่า {{scope}} ของคุณ", "marketplace.remove.cancel": "ยกเลิก", "marketplace.remove.confirm.button": "ลบ", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "ค้นหา...", + "marketplace.filter.all": "รายการทั้งหมด", + "marketplace.filter.notInstalled": "ยังไม่ได้ติดตั้ง", + "marketplace.empty": "ไม่พบรายการ", + "marketplace.badge.mcpServer": "เซิร์ฟเวอร์ MCP", + "marketplace.badge.mode": "โหมด", + "marketplace.card.by": "โดย {{author}}", + "marketplace.install.method": "วิธีการติดตั้ง", + "marketplace.install.parameters": "พารามิเตอร์", + "marketplace.install.optional": "(ไม่บังคับ)", + "marketplace.install.required": "จำเป็นต้องใช้ {{name}}", + "marketplace.scope.project": "โปรเจ็กต์", + "marketplace.scope.global": "โกลบอล", + "marketplace.remove.type.mcp": "เซิร์ฟเวอร์ MCP", + "marketplace.remove.type.skill": "ทักษะ", + "marketplace.remove.type.mode": "โหมด", + "marketplace.remove.failed": "ไม่สามารถลบ {{name}} ได้", + "marketplace.install": "ติดตั้ง", + "marketplace.filter.installed": "ติดตั้งแล้ว", + "marketplace.error.dismiss": "ปิด", } diff --git a/packages/kilo-i18n/src/tr.ts b/packages/kilo-i18n/src/tr.ts index 1b4c19c6fda..5dff96caf50 100644 --- a/packages/kilo-i18n/src/tr.ts +++ b/packages/kilo-i18n/src/tr.ts @@ -14,4 +14,54 @@ export const dict = { // Reasoning block label "ui.reasoning.label": "Akıl Yürütme", + + // Marketplace + "marketplace.tab.skills": "Yetenekler", + "marketplace.tab.mcpServers": "MCP Sunucuları", + "marketplace.tab.modes": "Modlar", + "marketplace.category.all": "Tümü", + "marketplace.placeholder": "Uygulanacak", + "marketplace.card.installed": "Yüklendi", + "marketplace.card.install": "Yükle", + "marketplace.card.remove": "Kaldır", + "marketplace.card.removeScope": "Kaldır ({{scope}})", + "marketplace.card.showMore": "Daha fazla göster", + "marketplace.card.showLess": "Daha az göster", + "marketplace.install.title": "{{name}} yükle", + "marketplace.install.scope": "Kapsam", + "marketplace.install.scope.project": "Proje", + "marketplace.install.scope.global": "Genel", + "marketplace.install.prerequisites": "Ön koşullar", + "marketplace.install.installing": "Yükleniyor...", + "marketplace.install.cancel": "İptal", + "marketplace.install.success": "Başarıyla yüklendi!", + "marketplace.install.failed": "Yükleme başarısız oldu", + "marketplace.install.done": "Bitti", + "marketplace.install.close": "Kapat", + "marketplace.remove.title": "{{name}} kaldırılsın mı?", + "marketplace.remove.confirm": + "Bu {{type}} öğesini kaldırmak istediğinizden emin misiniz? Bu, {{scope}} yapılandırmanızdan kaldırılacaktır.", + "marketplace.remove.cancel": "İptal", + "marketplace.remove.confirm.button": "Kaldır", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "Ara...", + "marketplace.filter.all": "Tüm Öğeler", + "marketplace.filter.notInstalled": "Yüklü Değil", + "marketplace.empty": "Öğe bulunamadı", + "marketplace.badge.mcpServer": "MCP Sunucusu", + "marketplace.badge.mode": "Mod", + "marketplace.card.by": "{{author}} tarafından", + "marketplace.install.method": "Yükleme Yöntemi", + "marketplace.install.parameters": "Parametreler", + "marketplace.install.optional": "(isteğe bağlı)", + "marketplace.install.required": "{{name}} gereklidir", + "marketplace.scope.project": "proje", + "marketplace.scope.global": "genel", + "marketplace.remove.type.mcp": "MCP sunucusu", + "marketplace.remove.type.skill": "yetenek", + "marketplace.remove.type.mode": "mod", + "marketplace.remove.failed": "{{name}} kaldırılamadı", + "marketplace.install": "Yükle", + "marketplace.filter.installed": "Yüklendi", + "marketplace.error.dismiss": "Kapat", } diff --git a/packages/kilo-i18n/src/zh.ts b/packages/kilo-i18n/src/zh.ts index a5f9d8870d3..f6ab44e6cf8 100644 --- a/packages/kilo-i18n/src/zh.ts +++ b/packages/kilo-i18n/src/zh.ts @@ -17,14 +17,14 @@ export const dict = { "marketplace.tab.skills": "技能", "marketplace.tab.mcpServers": "MCP 服务器", "marketplace.tab.modes": "模式", - "marketplace.search.skills": "搜索技能...", "marketplace.category.all": "全部", - "marketplace.empty.skills": "未找到技能", "marketplace.placeholder": "待实现", "marketplace.card.installed": "已安装", "marketplace.card.install": "安装", "marketplace.card.remove": "移除", "marketplace.card.removeScope": "移除 ({{scope}})", + "marketplace.card.showMore": "展开", + "marketplace.card.showLess": "收起", "marketplace.install.title": "安装 {{name}}", "marketplace.install.scope": "作用域", "marketplace.install.scope.project": "项目", @@ -40,4 +40,25 @@ export const dict = { "marketplace.remove.confirm": "确定要移除此 {{type}} 吗?这会将其从你的 {{scope}} 配置中移除。", "marketplace.remove.cancel": "取消", "marketplace.remove.confirm.button": "移除", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "搜索...", + "marketplace.filter.all": "所有项目", + "marketplace.filter.notInstalled": "未安装", + "marketplace.empty": "未找到项目", + "marketplace.badge.mcpServer": "MCP 服务器", + "marketplace.badge.mode": "模式", + "marketplace.card.by": "作者:{{author}}", + "marketplace.install.method": "安装方法", + "marketplace.install.parameters": "参数", + "marketplace.install.optional": "(可选)", + "marketplace.install.required": "{{name}} 是必填项", + "marketplace.scope.project": "项目", + "marketplace.scope.global": "全局", + "marketplace.remove.type.mcp": "MCP 服务器", + "marketplace.remove.type.skill": "技能", + "marketplace.remove.type.mode": "模式", + "marketplace.remove.failed": "移除 {{name}} 失败", + "marketplace.install": "安装", + "marketplace.filter.installed": "已安装", + "marketplace.error.dismiss": "关闭", } diff --git a/packages/kilo-i18n/src/zht.ts b/packages/kilo-i18n/src/zht.ts index 219ed9d7f95..1f123464e9a 100644 --- a/packages/kilo-i18n/src/zht.ts +++ b/packages/kilo-i18n/src/zht.ts @@ -17,14 +17,14 @@ export const dict = { "marketplace.tab.skills": "技能", "marketplace.tab.mcpServers": "MCP 伺服器", "marketplace.tab.modes": "模式", - "marketplace.search.skills": "搜尋技能...", "marketplace.category.all": "全部", - "marketplace.empty.skills": "找不到技能", "marketplace.placeholder": "待實作", "marketplace.card.installed": "已安裝", "marketplace.card.install": "安裝", "marketplace.card.remove": "移除", "marketplace.card.removeScope": "移除 ({{scope}})", + "marketplace.card.showMore": "展開", + "marketplace.card.showLess": "收起", "marketplace.install.title": "安裝 {{name}}", "marketplace.install.scope": "作用域", "marketplace.install.scope.project": "專案", @@ -40,4 +40,25 @@ export const dict = { "marketplace.remove.confirm": "確定要移除此 {{type}} 嗎?這會將其從你的 {{scope}} 設定中移除。", "marketplace.remove.cancel": "取消", "marketplace.remove.confirm.button": "移除", + "marketplace.tab.mcp": "MCP", + "marketplace.search": "搜尋...", + "marketplace.filter.all": "所有項目", + "marketplace.filter.notInstalled": "未安裝", + "marketplace.empty": "找不到項目", + "marketplace.badge.mcpServer": "MCP 伺服器", + "marketplace.badge.mode": "模式", + "marketplace.card.by": "作者:{{author}}", + "marketplace.install.method": "安裝方法", + "marketplace.install.parameters": "參數", + "marketplace.install.optional": "(選填)", + "marketplace.install.required": "{{name}} 是必填項", + "marketplace.scope.project": "專案", + "marketplace.scope.global": "全域", + "marketplace.remove.type.mcp": "MCP 伺服器", + "marketplace.remove.type.skill": "技能", + "marketplace.remove.type.mode": "模式", + "marketplace.remove.failed": "移除 {{name}} 失敗", + "marketplace.install": "安裝", + "marketplace.filter.installed": "已安裝", + "marketplace.error.dismiss": "關閉", } diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 6393350eca2..0a514ed09e3 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -1,6 +1,7 @@ import * as path from "path" import * as vscode from "vscode" import { z } from "zod" +import { buildPreviewPath, getPreviewCommand, getPreviewDir, parseImage, trimEntries } from "./image-preview" import { isAbsolutePath } from "./path-utils" import type { KiloClient, @@ -438,6 +439,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "openSubAgentViewer": vscode.commands.executeCommand("kilo-code.new.openSubAgentViewer", message.sessionID, message.title) break + case "previewImage": + this.handlePreviewImage(message.dataUrl, message.filename) + break case "openFile": if (message.filePath) { this.handleOpenFile(message.filePath, message.line, message.column) @@ -667,7 +671,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper const workspace = this.getProjectDirectory(this.currentSession?.id) const scope = message.mpInstallOptions?.target ?? "project" const result = await this.getMarketplace().install(message.mpItem, message.mpInstallOptions, workspace) - if (result.success) await this.disposeCliInstance(scope) + if (result.success) { + await this.disposeCliInstance(scope) + this.cachedAgentsMessage = null + await this.fetchAndSendAgents() + } this.postMessage({ type: "marketplaceInstallResult", success: result.success, @@ -680,7 +688,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper const workspace = this.getProjectDirectory(this.currentSession?.id) const scope = message.mpInstallOptions?.target ?? "project" const result = await this.getMarketplace().remove(message.mpItem, scope, workspace) - if (result.success) await this.disposeCliInstance(scope) + if (result.success) { + await this.disposeCliInstance(scope) + this.cachedAgentsMessage = null + await this.fetchAndSendAgents() + } this.postMessage({ type: "marketplaceRemoveResult", success: result.success, @@ -1246,22 +1258,34 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper */ private async handleRemoveMode(name: string): Promise { if (!this.client) return + let removed = false + + // 1. Try CLI removal (handles .md files and legacy .kilocodemodes) try { const dir = this.getWorkspaceDirectory() const result = await this.client.kilocode.removeAgent({ name, directory: dir }) - if (result.error) { - console.error("[Kilo New] KiloProvider: removeAgent returned error:", result.error) - this.cachedAgentsMessage = null - await this.fetchAndSendAgents() - return - } - } catch (error) { - console.error("[Kilo New] KiloProvider: Failed to remove mode:", error) - this.cachedAgentsMessage = null - await this.fetchAndSendAgents() - return + if (!result.error) removed = true + } catch { + // CLI removal failed — agent may be in kilo.json instead } - // Invalidate cache so next requestAgents fetches fresh data + + // 2. Try removing from kilo.json (handles marketplace-installed modes) + if (!removed) { + const workspace = this.getProjectDirectory(this.currentSession?.id) + const mp = this.getMarketplace() + const stub = { id: name, type: "mode" as const, name, description: "", content: "" } + const project = await mp.remove(stub, "project", workspace) + const global = await mp.remove(stub, "global", workspace) + if (project.success || global.success) { + await this.disposeCliInstance("global") + removed = true + } + } + + if (!removed) { + console.error("[Kilo New] KiloProvider: Failed to remove mode:", name) + } + this.cachedAgentsMessage = null await this.fetchAndSendAgents() } @@ -1277,12 +1301,13 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper await this.client.global.dispose().catch((e: unknown) => { console.warn("[Kilo New] global.dispose() after marketplace change failed:", e) }) - } else { - const dir = this.getWorkspaceDirectory() - await this.client.instance.dispose({ directory: dir }).catch((e: unknown) => { - console.warn("[Kilo New] instance.dispose() after marketplace change failed:", e) - }) } + // Always dispose the per-project instance so it rebuilds state from + // the (possibly updated) global + project config on the next request. + const dir = this.getWorkspaceDirectory() + await this.client.instance.dispose({ directory: dir }).catch((e: unknown) => { + console.warn("[Kilo New] instance.dispose() after marketplace change failed:", e) + }) } /** @@ -2016,6 +2041,44 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + private handlePreviewImage(dataUrl: string, filename: string): void { + const dir = this.extensionContext?.globalStorageUri + if (!dir) return + + const img = parseImage(dataUrl, filename) + if (!img) return + + const root = vscode.Uri.joinPath(dir, getPreviewDir()) + const uri = vscode.Uri.joinPath(dir, buildPreviewPath(img.name, Date.now())) + const clean = () => + vscode.workspace.fs.readDirectory(root).then( + (items) => { + const stale = trimEntries(items.map(([name]) => ({ path: name }))) + return Promise.all( + stale.map((name) => + Promise.resolve(vscode.workspace.fs.delete(vscode.Uri.joinPath(root, name), { recursive: true })).then( + undefined, + (err: unknown) => { + console.warn("[Kilo New] KiloProvider: Failed to delete stale preview:", err) + }, + ), + ), + ) + }, + () => [], + ) + const open = () => + vscode.commands + .executeCommand(...getPreviewCommand(uri)) + .then(undefined, () => vscode.commands.executeCommand("vscode.open", uri)) + + void vscode.workspace.fs + .createDirectory(root) + .then(() => vscode.workspace.fs.writeFile(uri, img.data)) + .then(() => clean()) + .then(open, (err) => console.error("[Kilo New] KiloProvider: Failed to preview image:", err)) + } + /** * Handle openFile request from the webview — open a file in the VS Code editor. * Resolves relative paths against the current session's directory (which may be diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 26d455c5b18..ee235e930fa 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -66,6 +66,7 @@ export class AgentManagerProvider implements vscode.Disposable { constructor( private readonly extensionUri: vscode.Uri, private readonly connectionService: KiloConnectionService, + private readonly context: vscode.ExtensionContext, ) { this.outputChannel = vscode.window.createOutputChannel("Kilo Agent Manager") this.terminalManager = new SessionTerminalManager( @@ -147,7 +148,7 @@ export class AgentManagerProvider implements vscode.Disposable { panel.webview.html = this.getHtml(panel.webview) - this.provider = new KiloProvider(this.extensionUri, this.connectionService) + this.provider = new KiloProvider(this.extensionUri, this.connectionService, this.context) this.provider.attachToWebview(panel.webview, { onBeforeMessage: (msg) => this.onMessage(msg), }) @@ -232,6 +233,9 @@ export class AgentManagerProvider implements vscode.Disposable { this.openWorktreeDirectory(m.worktreeId) return null } + if (m.type === "previewImage") { + return msg + } if (m.type === "agentManager.showExistingLocalTerminal") { this.terminalManager.syncLocalOnSessionSwitch() return null diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index b6cf1cc138d..91977a40469 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -379,6 +379,12 @@ interface GenericOpenFileIn { column?: number } +interface PreviewImageIn { + type: "previewImage" + dataUrl: string + filename: string +} + interface LoadMessagesIn { type: "loadMessages" sessionID: string @@ -434,6 +440,7 @@ export type AgentManagerInMessage = | StopDiffWatchIn | OpenFileIn | GenericOpenFileIn + | PreviewImageIn | LoadMessagesIn | ClearSessionIn | AbortIn diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 457d0901d7f..c5fa591e2be 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -47,7 +47,7 @@ export function activate(context: vscode.ExtensionContext) { ) // Create Agent Manager provider for editor panel - const agentManagerProvider = new AgentManagerProvider(context.extensionUri, connectionService) + const agentManagerProvider = new AgentManagerProvider(context.extensionUri, connectionService, context) context.subscriptions.push(agentManagerProvider) // Register serializer so Agent Manager restores when VS Code restarts diff --git a/packages/kilo-vscode/src/image-preview.ts b/packages/kilo-vscode/src/image-preview.ts new file mode 100644 index 00000000000..c9abc2d4331 --- /dev/null +++ b/packages/kilo-vscode/src/image-preview.ts @@ -0,0 +1,83 @@ +import * as path from "path" + +const IMAGE_PREVIEW_ID = "imagePreview.previewEditor" +const PREVIEW_DIR = "image-preview" +const PREVIEW_LIMIT = 20 + +type Preview = { + data: Uint8Array + ext: string + name: string +} + +export function parseImage(dataUrl: string, filename: string): Preview | null { + const sep = dataUrl.indexOf(",") + if (sep === -1) return null + + const head = dataUrl.slice(0, sep) + const mime = head.match(/^data:(image\/[A-Za-z0-9.+-]+);base64$/)?.[1] + if (!mime) return null + + const data = parseBase64(dataUrl.slice(sep + 1)) + if (!data) return null + + const ext = getExt(mime) + const name = buildName(filename, ext) + return { data, ext, name } +} + +export function buildPreviewPath(name: string, now: number): string { + return path.posix.join(PREVIEW_DIR, `${now}-${name}`) +} + +export function getPreviewCommand(uri: { + toString(): string +}): [string, { resource: { toString(): string }; size: "contain" }] { + return [IMAGE_PREVIEW_ID, { resource: uri, size: "contain" }] +} + +export function getPreviewDir(): string { + return PREVIEW_DIR +} + +export function trimEntries(items: T[], limit = PREVIEW_LIMIT): string[] { + if (items.length <= limit) return [] + + return items + .slice() + .sort((a, b) => a.path.localeCompare(b.path)) + .slice(0, items.length - limit) + .map((item) => item.path) +} + +function parseBase64(value: string): Uint8Array | null { + try { + return Buffer.from(value, "base64") + } catch { + return null + } +} + +function getExt(mime: string): string { + const raw = mime.split("/")[1] ?? "png" + if (raw === "jpeg") return "jpg" + if (raw === "svg+xml") return "svg" + return raw +} + +function buildName(filename: string, ext: string): string { + const raw = path.basename(filename || "image") + const item = path.parse(raw) + const base = sanitize(item.name) || "image" + const tail = sanitize(item.ext.replace(/^\./, "")) + const clean = tail ? `${base}.${tail}` : base + if (path.extname(clean)) return clean + return `${clean}.${ext}` +} + +function sanitize(value: string): string { + return value + .replace(/[^A-Za-z0-9._-]+/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, "") +} diff --git a/packages/kilo-vscode/src/services/marketplace/installer.ts b/packages/kilo-vscode/src/services/marketplace/installer.ts index 7cf55351ef5..b1d2bcb9b58 100644 --- a/packages/kilo-vscode/src/services/marketplace/installer.ts +++ b/packages/kilo-vscode/src/services/marketplace/installer.ts @@ -3,9 +3,13 @@ import * as path from "path" import * as os from "os" import { execFile } from "child_process" import { promisify } from "util" +import * as yaml from "yaml" import type { MarketplaceItem, SkillMarketplaceItem, + McpMarketplaceItem, + ModeMarketplaceItem, + McpInstallationMethod, InstallMarketplaceItemOptions, InstallResult, RemoveResult, @@ -24,9 +28,87 @@ export class MarketplaceInstaller { ): Promise { const scope = options.target ?? "project" if (item.type === "skill") return this.installSkill(item, scope, workspace) - return { success: false, slug: item.id, error: `${item.type} installation not yet implemented` } + if (item.type === "mcp") return this.installMcp(item, options, scope, workspace) + return this.installMode(item, scope, workspace) } + // ── MCP ───────────────────────────────────────────────────────────── + + async installMcp( + item: McpMarketplaceItem, + options: InstallMarketplaceItemOptions, + scope: "project" | "global", + workspace?: string, + ): Promise { + if (scope === "project" && !workspace) { + return { success: false, slug: item.id, error: "No workspace directory for project-scope install" } + } + + const config = await this.readConfig(scope, workspace) + if (!config.mcp) config.mcp = {} + + if (config.mcp[item.id]) { + return { success: false, slug: item.id, error: "MCP server already installed. Remove it first." } + } + + const content = this.resolveMcpContent(item, options) + if (!content) { + return { success: false, slug: item.id, error: "No installation content for MCP server" } + } + + try { + config.mcp[item.id] = this.buildMcpEntry(content, options.parameters) + } catch (err) { + return { success: false, slug: item.id, error: `Invalid MCP config: ${err}` } + } + + await this.writeConfig(scope, workspace, config) + return { success: true, slug: item.id } + } + + private resolveMcpContent(item: McpMarketplaceItem, options: InstallMarketplaceItemOptions): string | undefined { + if (typeof item.content === "string") return item.content + if (!Array.isArray(item.content) || item.content.length === 0) return undefined + const name = options.parameters?.__method as string | undefined + if (name) { + const found = item.content.find((m: McpInstallationMethod) => m.name === name) + if (found) return found.content + } + return item.content[0].content + } + + private buildMcpEntry(content: string, params?: Record): Record { + const filtered = Object.fromEntries(Object.entries(params ?? {}).filter(([k]) => k !== "__method")) + const replaced = Object.keys(filtered).length > 0 ? substituteParams(content, filtered) : content + return JSON.parse(replaced) + } + + // ── Mode ──────────────────────────────────────────────────────────── + + async installMode( + item: ModeMarketplaceItem, + scope: "project" | "global", + workspace?: string, + ): Promise { + if (scope === "project" && !workspace) { + return { success: false, slug: item.id, error: "No workspace directory for project-scope install" } + } + + const config = await this.readConfig(scope, workspace) + if (!config.agent) config.agent = {} + + if (config.agent[item.id]) { + return { success: false, slug: item.id, error: "Mode already installed. Remove it first." } + } + + config.agent[item.id] = convertModeToAgent(item.content) + + await this.writeConfig(scope, workspace, config) + return { success: true, slug: item.id } + } + + // ── Skill ─────────────────────────────────────────────────────────── + async installSkill( item: SkillMarketplaceItem, scope: "project" | "global", @@ -106,9 +188,34 @@ export class MarketplaceInstaller { } } + // ── Remove ────────────────────────────────────────────────────────── + async remove(item: MarketplaceItem, scope: "project" | "global", workspace?: string): Promise { if (item.type === "skill") return this.removeSkill(item, scope, workspace) - return { success: false, slug: item.id, error: `${item.type} removal not yet implemented` } + if (item.type === "mcp") return this.removeMcp(item, scope, workspace) + return this.removeMode(item, scope, workspace) + } + + async removeMcp(item: McpMarketplaceItem, scope: "project" | "global", workspace?: string): Promise { + const config = await this.readConfig(scope, workspace) + if (!config.mcp?.[item.id]) { + return { success: true, slug: item.id } + } + delete config.mcp[item.id] + if (Object.keys(config.mcp).length === 0) delete config.mcp + await this.writeConfig(scope, workspace, config) + return { success: true, slug: item.id } + } + + async removeMode(item: ModeMarketplaceItem, scope: "project" | "global", workspace?: string): Promise { + const config = await this.readConfig(scope, workspace) + if (!config.agent?.[item.id]) { + return { success: true, slug: item.id } + } + delete config.agent[item.id] + if (Object.keys(config.agent).length === 0) delete config.agent + await this.writeConfig(scope, workspace, config) + return { success: true, slug: item.id } } async removeSkill( @@ -136,6 +243,32 @@ export class MarketplaceInstaller { return { success: false, slug: item.id, error: String(err) } } } + + // ── Config helpers ────────────────────────────────────────────────── + + private async readConfig( + scope: "project" | "global", + workspace?: string, + ): Promise>> { + const filepath = this.paths.configPath(scope, workspace) + try { + const content = await fs.readFile(filepath, "utf-8") + return JSON.parse(content) + } catch (err) { + if ((err as NodeJS.ErrnoException).code === "ENOENT") return {} + throw err + } + } + + private async writeConfig( + scope: "project" | "global", + workspace: string | undefined, + config: Record, + ): Promise { + const filepath = this.paths.configPath(scope, workspace) + await fs.mkdir(path.dirname(filepath), { recursive: true }) + await fs.writeFile(filepath, JSON.stringify(config, null, 2) + "\n", "utf-8") + } } // ── Helpers ───────────────────────────────────────────────────────── @@ -145,6 +278,66 @@ function isSafeId(id: string): boolean { return /^[\w\-@.]+$/.test(id) } +// Group name → opencode permission key mapping (mirrors ModesMigrator) +const GROUP_PERMISSIONS: Record = { + read: "read", + edit: "edit", + browser: "bash", + command: "bash", + mcp: "mcp", +} +const ALL_PERMISSIONS = ["read", "edit", "bash", "mcp"] + +function convertModeToAgent(content: string): Record { + const mode = yaml.parse(content) as Record + const groups = (mode.groups ?? []) as Array]> + + const permission: Record = {} + const allowed = new Set() + for (const group of groups) { + if (typeof group === "string") { + const key = GROUP_PERMISSIONS[group] ?? group + allowed.add(key) + permission[key] = "allow" + } else if (Array.isArray(group)) { + const [name, cfg] = group + const key = GROUP_PERMISSIONS[name] ?? name + allowed.add(key) + permission[key] = cfg?.fileRegex ? { [String(cfg.fileRegex)]: "allow", "*": "deny" } : "allow" + } + } + for (const perm of ALL_PERMISSIONS) { + if (!allowed.has(perm)) permission[perm] = "deny" + } + + const prompt = [mode.roleDefinition, mode.customInstructions].filter(Boolean).join("\n\n") + return { + mode: "primary", + description: mode.description ?? mode.whenToUse ?? mode.name, + prompt, + permission, + } +} + +function escapeJsonValue(raw: string): string { + return raw + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/\t/g, "\\t") +} + +function substituteParams(template: string, params: Record): string { + let result = template + for (const [key, value] of Object.entries(params)) { + const escaped = escapeJsonValue(String(value ?? "")) + result = result.replaceAll(`{{${key}}}`, escaped) + result = result.replaceAll(`\${${key}}`, escaped) + } + return result +} + async function findEscapedPaths(dir: string): Promise { const resolved = path.resolve(dir) const escaped: string[] = [] diff --git a/packages/kilo-vscode/src/services/telemetry/types.ts b/packages/kilo-vscode/src/services/telemetry/types.ts index 5c7e106fd52..74c676a2204 100644 --- a/packages/kilo-vscode/src/services/telemetry/types.ts +++ b/packages/kilo-vscode/src/services/telemetry/types.ts @@ -32,8 +32,10 @@ export enum TelemetryEventName { PROMPT_ENHANCED = "Prompt Enhanced", // Marketplace + MARKETPLACE_INSTALL_BUTTON_CLICKED = "Marketplace Install Button Clicked", MARKETPLACE_ITEM_INSTALLED = "Marketplace Item Installed", MARKETPLACE_ITEM_REMOVED = "Marketplace Item Removed", + MARKETPLACE_TAB_VIEWED = "Marketplace Tab Viewed", // Account & Auth ACCOUNT_CONNECT_CLICKED = "Account Connect Clicked", diff --git a/packages/kilo-vscode/tests/setup/vscode-mock.ts b/packages/kilo-vscode/tests/setup/vscode-mock.ts index adf1dfafda8..8fdba932d30 100644 --- a/packages/kilo-vscode/tests/setup/vscode-mock.ts +++ b/packages/kilo-vscode/tests/setup/vscode-mock.ts @@ -51,6 +51,14 @@ const mockVscode = { update: async () => {}, }), asRelativePath: (pathOrUri: string) => pathOrUri, + fs: { + createDirectory: async () => {}, + writeFile: async () => {}, + readFile: async () => new Uint8Array(), + readDirectory: async () => [], + delete: async () => {}, + stat: async () => ({ type: 1, ctime: 0, mtime: 0, size: 0 }), + }, }, window: { activeTextEditor: undefined, diff --git a/packages/kilo-vscode/tests/unit/image-preview.test.ts b/packages/kilo-vscode/tests/unit/image-preview.test.ts new file mode 100644 index 00000000000..6e52993b032 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/image-preview.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from "bun:test" +import { buildPreviewPath, getPreviewCommand, getPreviewDir, parseImage, trimEntries } from "../../src/image-preview" + +describe("parseImage", () => { + it("parses png data urls and preserves a clean extension", () => { + const img = parseImage("data:image/png;base64,aGVsbG8=", "screen") + + expect(img).not.toBeNull() + expect(img?.name).toBe("screen.png") + expect(img?.ext).toBe("png") + expect(Buffer.from(img?.data ?? []).toString("utf8")).toBe("hello") + }) + + it("sanitizes the basename and keeps the existing extension", () => { + const img = parseImage("data:image/jpeg;base64,aGVsbG8=", "../../bad name!!.jpeg") + + expect(img).not.toBeNull() + expect(img?.name).toBe("bad-name.jpeg") + expect(img?.ext).toBe("jpg") + }) + + it("returns null for non-image data urls", () => { + expect(parseImage("data:text/plain;base64,aGVsbG8=", "note.txt")).toBeNull() + }) + + it("returns null when the header is not base64", () => { + expect(parseImage("data:image/png,hello", "screen.png")).toBeNull() + }) +}) + +describe("buildPreviewPath", () => { + it("writes previews into a dedicated storage folder", () => { + expect(buildPreviewPath("screen.png", 42)).toBe("image-preview/42-screen.png") + }) +}) + +describe("getPreviewDir", () => { + it("returns the preview storage folder", () => { + expect(getPreviewDir()).toBe("image-preview") + }) +}) + +describe("trimEntries", () => { + it("drops the oldest preview paths once the limit is exceeded", () => { + const items = Array.from({ length: 22 }, (_, i) => ({ path: `${String(i).padStart(2, "0")}-screen.png` })) + + expect(trimEntries(items)).toEqual(["00-screen.png", "01-screen.png"]) + }) + + it("keeps all preview paths when below the limit", () => { + expect(trimEntries([{ path: "01-screen.png" }])).toEqual([]) + }) +}) + +describe("getPreviewCommand", () => { + it("targets the built-in preview editor with explicit sizing", () => { + const uri = { toString: () => "file:///tmp/screen.png" } + + expect(getPreviewCommand(uri)).toEqual(["imagePreview.previewEditor", { resource: uri, size: "contain" }]) + }) +}) diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-skill-card-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-skill-card-chromium-linux.png index 2822662560f..86b45cb700e 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-skill-card-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-skill-card-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bc9274e748f94e0ae979f688a87c52b9a3d579ac015a2a49f327f8c96d002db -size 11116 +oid sha256:0d5400d9db070953fcbf4cc3f8a4968b80c5bccb1a67ee63a463aa0539de1f41 +size 11190 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-installed-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-installed-chromium-linux.png index 204b46b36ef..b7d88719025 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-installed-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-installed-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be90b297cbdef33fa1c3c67be7a42e4c00a5f3d68147a5fbf54a04ab7b261d04 -size 52330 +oid sha256:782c1fae4bed19825b56570e573d09cc80aa2180ca5c6a4c495867f207d95beb +size 54728 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-items-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-items-chromium-linux.png index 1bda64ef203..899e1a2c4ad 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-items-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-items-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d27dc2c85a377f7daab0b7de444a7d883a4503ab45c92c0c0a9155de89e2a221 -size 49775 +oid sha256:3d309302b177f97f198a2b889c96d08cf4578d219043d48a9d0251460be411c4 +size 52039 diff --git a/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx b/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx index 274e38c9076..1fb48cb6c6b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx @@ -247,7 +247,14 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran {(img) => (
- {img.filename} + {img.filename} + vscode.postMessage({ type: "previewImage", dataUrl: img.dataUrl, filename: img.filename }) + } + /> + )} + +
+ + + + + } + > + 0} fallback={

{props.emptyMessage}

}> +
+ + {(item) => { + const skill = item.type === "skill" ? (item as SkillMarketplaceItem) : undefined + const mcp = item.type === "mcp" ? (item as McpMarketplaceItem) : undefined + return ( + {(tag) => {tag}}} + /> + ) + }} + +
+
+
+ + ) +} diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx index e821a6e0beb..295b081bd78 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx @@ -1,168 +1,204 @@ -import { Component, createSignal, createMemo, onMount, onCleanup, Show } from "solid-js" +import { createSignal, createMemo, createEffect, onCleanup, onMount, Show } from "solid-js" import { Tabs } from "@kilocode/kilo-ui/tabs" +import { Card } from "@kilocode/kilo-ui/card" +import { Button } from "@kilocode/kilo-ui/button" import { useVSCode } from "../../context/vscode" import { useServer } from "../../context/server" import { useLanguage } from "../../context/language" -import type { MarketplaceItem, SkillMarketplaceItem, MarketplaceInstalledMetadata } from "../../types/marketplace" -import SkillsMarketplace from "./SkillsMarketplace" -import InstallModal from "./InstallModal" -import RemoveDialog from "./RemoveDialog" +import { useDialog } from "@kilocode/kilo-ui/context/dialog" +import type { + MarketplaceItem, + McpMarketplaceItem, + ModeMarketplaceItem, + SkillMarketplaceItem, + MarketplaceInstalledMetadata, +} from "../../types/marketplace" +import { TelemetryEventName } from "../../../../src/services/telemetry/types" +import { MarketplaceListView } from "./MarketplaceListView" +import { InstallModal } from "./InstallModal" +import { RemoveDialog } from "./RemoveDialog" import "./marketplace.css" const EMPTY_METADATA: MarketplaceInstalledMetadata = { project: {}, global: {} } -const MarketplaceView: Component = () => { +export const MarketplaceView = () => { const vscode = useVSCode() const server = useServer() const { t } = useLanguage() + const dialog = useDialog() const [items, setItems] = createSignal([]) const [metadata, setMetadata] = createSignal(EMPTY_METADATA) const [fetching, setFetching] = createSignal(true) const [errors, setErrors] = createSignal([]) - const [tab, setTab] = createSignal("skill") - - // Install/Remove state - const [installItem, setInstallItem] = createSignal(null) - const [removeItem, setRemoveItem] = createSignal<{ item: MarketplaceItem; scope: "project" | "global" } | null>(null) - const [pendingRemove, setPendingRemove] = createSignal(null) - const [removeError, setRemoveError] = createSignal(null) + const [tab, setTab] = createSignal("mcp") + const [pending, setPending] = createSignal<{ item: MarketplaceItem; scope: "project" | "global" } | null>(null) const skills = createMemo(() => items().filter((i): i is SkillMarketplaceItem => i.type === "skill")) + const mcps = createMemo(() => items().filter((i): i is McpMarketplaceItem => i.type === "mcp")) + const modes = createMemo(() => items().filter((i): i is ModeMarketplaceItem => i.type === "mode")) const fetchData = () => { setFetching(true) vscode.postMessage({ type: "fetchMarketplaceData" }) } - onMount(() => { - fetchData() - - const handler = (event: MessageEvent) => { - const msg = event.data - if (msg?.type === "marketplaceData") { + // Listen for messages + createEffect(() => { + const unsub = vscode.onMessage((msg) => { + if (msg.type === "marketplaceData") { setItems(msg.marketplaceItems ?? []) setMetadata(msg.marketplaceInstalledMetadata ?? EMPTY_METADATA) setErrors(msg.errors ?? []) setFetching(false) } - if (msg?.type === "marketplaceRemoveResult") { - if (msg.slug === pendingRemove()) { - setPendingRemove(null) - if (!msg.success) { - setRemoveError(msg.error ?? "Failed to remove item") - } else { - fetchData() + if (msg.type === "marketplaceRemoveResult") { + const removed = pending() + setPending(null) + if (msg.success) { + if (removed) { + telemetry(TelemetryEventName.MARKETPLACE_ITEM_REMOVED, { + itemId: removed.item.id, + itemType: removed.item.type, + itemName: removed.item.name, + target: removed.scope, + }) } + fetchData() + } else { + setErrors((prev) => [...prev, msg.error ?? t("marketplace.remove.failed", { name: msg.slug })]) } } - // Re-fetch when agents or skills change (e.g. after install via CLI) - if (msg?.type === "agentsLoaded" || msg?.type === "skillsLoaded") { - fetchData() - } - // Re-fetch when workspace directory changes - if (msg?.type === "workspaceDirectoryChanged") { - fetchData() - } - } - window.addEventListener("message", handler) - onCleanup(() => window.removeEventListener("message", handler)) + }) + onCleanup(unsub) + }) + + // Re-fetch when workspace changes + createEffect(() => { + server.workspaceDirectory() + fetchData() + }) + + const telemetry = (event: string, properties?: Record) => { + vscode.postMessage({ type: "telemetry", event, properties: properties ?? {} }) + } + + onMount(() => { + telemetry(TelemetryEventName.MARKETPLACE_TAB_VIEWED) }) const handleInstall = (item: MarketplaceItem) => { - setInstallItem(item) + telemetry(TelemetryEventName.MARKETPLACE_INSTALL_BUTTON_CLICKED, { + itemId: item.id, + itemType: item.type, + itemName: item.name, + }) + dialog.show(() => ( + dialog.close()} + onInstallResult={(success, scope, extra) => { + if (success) { + telemetry(TelemetryEventName.MARKETPLACE_ITEM_INSTALLED, { + itemId: item.id, + itemType: item.type, + itemName: item.name, + target: scope, + ...(extra?.hasParameters && { hasParameters: true }), + ...(extra?.installationMethodName && { installationMethodName: extra.installationMethodName }), + }) + dialog.close() + fetchData() + } + }} + /> + )) } const handleRemove = (item: MarketplaceItem, scope: "project" | "global") => { - setRemoveItem({ item, scope }) + dialog.show(() => ( + dialog.close()} + onConfirm={() => { + setPending({ item, scope }) + vscode.postMessage({ + type: "removeInstalledMarketplaceItem", + mpItem: item, + mpInstallOptions: { target: scope }, + }) + dialog.close() + }} + /> + )) } - const confirmRemove = () => { - const r = removeItem() - if (!r) return - setPendingRemove(r.item.id) - setRemoveError(null) - vscode.postMessage({ - type: "removeInstalledMarketplaceItem", - mpItem: r.item, - mpInstallOptions: { target: r.scope }, - }) - setRemoveItem(null) + const dismissError = (idx: number) => { + setErrors((prev) => prev.filter((_, i) => i !== idx)) } return (
- + 0}> + {errors().map((err, idx) => ( + + {err} + + + ))} + + + - {t("marketplace.tab.skills")} - {t("marketplace.tab.mcpServers")} + {t("marketplace.tab.mcp")} {t("marketplace.tab.modes")} + {t("marketplace.tab.skills")} - - - +
+ + + - -
-

{t("marketplace.placeholder")}

-
-
+ + + - -
-

{t("marketplace.placeholder")}

-
-
+ + + +
- - 0}> -
- {errors().map((e) => ( -

{e}

- ))} -
-
- - -
-

{removeError()}

-
-
- - - {(item) => ( - setInstallItem(null)} - onInstallResult={(success) => { - if (success) { - setInstallItem(null) - fetchData() - } - }} - /> - )} - - - - {(r) => ( - setRemoveItem(null)} - onConfirm={confirmRemove} - /> - )} -
) } - -export default MarketplaceView diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx index 4e544fac0cd..fb763cab520 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx @@ -1,4 +1,4 @@ -import { Component } from "solid-js" +import { Dialog } from "@kilocode/kilo-ui/dialog" import { Button } from "@kilocode/kilo-ui/button" import type { MarketplaceItem } from "../../types/marketplace" import { useLanguage } from "../../context/language" @@ -10,25 +10,26 @@ interface Props { onConfirm: () => void } -const RemoveDialog: Component = (props) => { +export const RemoveDialog = (props: Props) => { const { t } = useLanguage() + const typeName = () => { + if (props.item.type === "mcp") return t("marketplace.remove.type.mcp") + if (props.item.type === "mode") return t("marketplace.remove.type.mode") + return t("marketplace.remove.type.skill") + } + return ( -
-
e.stopPropagation()}> -

{t("marketplace.remove.title", { name: props.item.name })}

-

{t("marketplace.remove.confirm", { type: props.item.type, scope: props.scope })}

-
- - -
+ +

{t("marketplace.remove.confirm", { type: typeName(), scope: t(`marketplace.scope.${props.scope}`) })}

+
+ +
-
+ ) } - -export default RemoveDialog diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/SkillsMarketplace.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/SkillsMarketplace.tsx deleted file mode 100644 index e55c4d3419c..00000000000 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/SkillsMarketplace.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Component, createSignal, createMemo, For, Show } from "solid-js" -import { TextField } from "@kilocode/kilo-ui/text-field" -import { RadioGroup } from "@kilocode/kilo-ui/radio-group" -import { Tag } from "@kilocode/kilo-ui/tag" -import { Spinner } from "@kilocode/kilo-ui/spinner" -import type { SkillMarketplaceItem, MarketplaceInstalledMetadata, MarketplaceItem } from "../../types/marketplace" -import { useLanguage } from "../../context/language" -import ItemCard from "./ItemCard" - -interface Props { - items: SkillMarketplaceItem[] - metadata: MarketplaceInstalledMetadata - fetching: boolean - onInstall: (item: MarketplaceItem) => void - onRemove: (item: MarketplaceItem, scope: "project" | "global") => void -} - -const ALL = "__all__" - -const SkillsMarketplace: Component = (props) => { - const { t } = useLanguage() - const [search, setSearch] = createSignal("") - const [category, setCategory] = createSignal(ALL) - - const categories = createMemo(() => { - const set = new Set() - for (const item of props.items) { - if (item.displayCategory) set.add(item.displayCategory) - } - return [ALL, ...Array.from(set).sort()] - }) - - const filtered = createMemo(() => { - const q = search().toLowerCase() - const cat = category() - return props.items.filter((item) => { - if (cat !== ALL && item.displayCategory !== cat) return false - if (!q) return true - return ( - item.id.toLowerCase().includes(q) || - item.name.toLowerCase().includes(q) || - item.displayName.toLowerCase().includes(q) || - item.description.toLowerCase().includes(q) || - item.displayCategory.toLowerCase().includes(q) - ) - }) - }) - - return ( -
-
- -
-
- c} - label={(c: string) => (c === ALL ? t("marketplace.category.all") : c)} - onSelect={(v: string | undefined) => v && setCategory(v)} - /> -
- }> - 0} fallback={

{t("marketplace.empty.skills")}

}> -
- - {(item) => ( - {item.displayCategory}} - /> - )} - -
-
-
-
- ) -} - -export default SkillsMarketplace diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/index.ts b/packages/kilo-vscode/webview-ui/src/components/marketplace/index.ts index 979b85151d1..e322a99a16f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/index.ts +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/index.ts @@ -1 +1 @@ -export { default as MarketplaceView } from "./MarketplaceView" +export { MarketplaceView } from "./MarketplaceView" diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css index 49c95541ce4..77a7c0d306f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css @@ -6,6 +6,19 @@ height: 100%; padding: 12px; overflow: auto; + gap: 8px; +} + +.marketplace-tabs-root { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; +} + +.marketplace-content { + flex: 1; + overflow: auto; } /* List layout */ @@ -23,22 +36,59 @@ align-items: center; } +.marketplace-search-field { + flex: 1; +} + .marketplace-category-filters { overflow-x: auto; } +/* Tag filters */ + +.marketplace-active-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.marketplace-tag-filter { + all: unset; + cursor: pointer; + opacity: 0.5; + transition: opacity 0.15s ease; +} + +.marketplace-tag-filter:hover { + opacity: 0.8; +} + +.marketplace-tag-filter.active { + opacity: 1; +} + +/* Loading & empty */ + +.marketplace-loading { + display: flex; + justify-content: center; + align-items: center; + min-height: 200px; +} + +.marketplace-empty { + text-align: center; + color: var(--text-weak); + padding: 24px; +} + /* Grid */ .marketplace-grid { display: grid; - grid-template-columns: 1fr; + grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 12px; -} - -@media (min-width: 500px) { - .marketplace-grid { - grid-template-columns: repeat(2, 1fr); - } + max-width: 1200px; } /* Card */ @@ -48,9 +98,6 @@ flex-direction: column; gap: 8px; padding: 12px; - border: 1px solid var(--border-base); - border-radius: 6px; - background: var(--background-base); } .marketplace-card-header { @@ -71,11 +118,11 @@ color: var(--text-base); } -.marketplace-card-link { - text-decoration: none; +.marketplace-card-name.clickable { + cursor: pointer; } -.marketplace-card-link:hover { +.marketplace-card-name.clickable:hover { text-decoration: underline; } @@ -94,6 +141,22 @@ margin: 0; } +.marketplace-card-description.expanded { + display: block; + -webkit-line-clamp: unset; +} + +.marketplace-card-expand { + all: unset; + cursor: pointer; + font-size: 12px; + color: var(--text-interactive-base); +} + +.marketplace-card-expand:hover { + text-decoration: underline; +} + .marketplace-card-footer { display: flex; align-items: center; @@ -115,117 +178,109 @@ flex-shrink: 0; } -.marketplace-installed-tag { +/* Installed badge */ + +.marketplace-badge-installed { color: var(--text-on-success-base) !important; border-color: var(--border-success-base) !important; } +/* Remove button */ + .marketplace-remove-btn { color: var(--text-on-critical-base) !important; border-color: var(--border-critical-base) !important; } -/* Placeholder */ +/* Remove dialog actions */ -.marketplace-placeholder { - display: flex; - justify-content: center; - align-items: center; - min-height: 200px; - color: var(--text-weak); - font-size: 16px; -} - -/* Empty state */ - -.marketplace-empty { - text-align: center; - color: var(--text-weak); - padding: 24px; -} - -/* Modal / Dialog */ - -.marketplace-modal-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -} - -.marketplace-modal { - background: var(--background-base); - border: 1px solid var(--border-base); - border-radius: 8px; - padding: 20px; - max-width: 480px; - width: 90%; - max-height: 80vh; - overflow-y: auto; -} - -.marketplace-modal h3 { - margin: 0 0 16px; - font-size: 16px; - color: var(--text-base); -} - -.marketplace-modal p { - margin: 0 0 12px; - color: var(--text-weak); - font-size: 13px; -} - -.marketplace-modal-section { - margin-bottom: 16px; -} - -.marketplace-modal-section > label { - display: block; - font-weight: 600; - font-size: 13px; - margin-bottom: 6px; - color: var(--text-base); -} - -.marketplace-modal-actions { +.marketplace-remove-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; } -.marketplace-modal-result { - text-align: center; - padding: 12px 0; +/* Danger button (remove confirm) */ + +.danger-btn { + color: var(--text-on-critical-base) !important; + border-color: var(--border-critical-base) !important; } -.marketplace-prerequisites { +/* Install modal */ + +.install-modal-body { + display: flex; + flex-direction: column; + gap: 16px; +} + +.install-modal-section { + display: flex; + flex-direction: column; + gap: 6px; +} + +.install-modal-label { + font-weight: 600; + font-size: 13px; + color: var(--text-base); +} + +.install-modal-prerequisites { margin: 0; padding-left: 20px; font-size: 13px; color: var(--text-weak); } -/* Error/Success messages */ - -.marketplace-errors { - margin-top: 12px; +.install-modal-param { + margin-bottom: 8px; } -.marketplace-error { - color: var(--text-on-critical-base); - font-size: 13px; +.install-modal-footer { + display: flex; + justify-content: flex-end; + gap: 8px; } -.marketplace-success { +.install-modal-result { + text-align: center; + padding: 12px 0; +} + +.install-modal-success { color: var(--text-on-success-base); font-size: 14px; font-weight: 600; + margin: 0 0 16px; +} + +.install-modal-error-msg { + color: var(--text-on-critical-base); + font-size: 13px; + margin: 0 0 16px; +} + +/* Error banner */ + +.marketplace-error-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 8px 12px; + font-size: 13px; +} + +/* Link style */ + +.link { + color: var(--text-interactive-base); + cursor: pointer; +} + +.link:hover { + text-decoration: underline; } diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts index 264b722a54c..9504e734f37 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts @@ -1,11 +1,19 @@ import type { MarketplaceInstalledMetadata } from "../../types/marketplace" +export function isInstalled( + id: string, + type: string, + metadata: MarketplaceInstalledMetadata, +): "project" | "global" | false { + return installedScopes(id, type, metadata)[0] ?? false +} + export function installedScopes( id: string, type: string, metadata: MarketplaceInstalledMetadata, -): Array<"project" | "global"> { - const scopes: Array<"project" | "global"> = [] +): ("project" | "global")[] { + const scopes: ("project" | "global")[] = [] if (metadata.project[id]?.type === type) scopes.push("project") if (metadata.global[id]?.type === type) scopes.push("global") return scopes diff --git a/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx index 253d7b99018..c6c30fb9528 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx @@ -2,14 +2,14 @@ /** * Stories for Marketplace components. * - * Renders SkillsMarketplace and ItemCard directly with mock data + * Renders MarketplaceListView and ItemCard directly with mock data * so no API requests are made. */ import type { Meta, StoryObj } from "storybook-solidjs-vite" import { StoryProviders } from "./StoryProviders" -import SkillsMarketplace from "../components/marketplace/SkillsMarketplace" -import ItemCard from "../components/marketplace/ItemCard" +import { MarketplaceListView } from "../components/marketplace/MarketplaceListView" +import { ItemCard } from "../components/marketplace/ItemCard" import type { SkillMarketplaceItem, MarketplaceInstalledMetadata } from "../types/marketplace" import "../components/marketplace/marketplace.css" @@ -116,10 +116,13 @@ export const SkillsTabWithItems: Story = { render: () => (
- @@ -133,10 +136,13 @@ export const SkillsTabWithInstalled: Story = { render: () => (
- @@ -150,7 +156,16 @@ export const SkillsTabEmpty: Story = { render: () => (
- +
), diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index 4f57aeb4129..8dda627a49c 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -776,6 +776,7 @@ border-radius: 4px; border: 1px solid var(--vscode-widget-border, rgba(255, 255, 255, 0.1)); display: block; + cursor: pointer; } .image-attachment-remove { diff --git a/packages/kilo-vscode/webview-ui/src/types/marketplace.ts b/packages/kilo-vscode/webview-ui/src/types/marketplace.ts index d41a7351266..943d2bec10a 100644 --- a/packages/kilo-vscode/webview-ui/src/types/marketplace.ts +++ b/packages/kilo-vscode/webview-ui/src/types/marketplace.ts @@ -1,3 +1,17 @@ +export interface McpParameter { + name: string + key: string + placeholder?: string + optional?: boolean +} + +export interface McpInstallationMethod { + name: string + content: string + parameters?: McpParameter[] + prerequisites?: string[] +} + export interface MarketplaceItemBase { id: string name: string @@ -11,7 +25,8 @@ export interface MarketplaceItemBase { export interface McpMarketplaceItem extends MarketplaceItemBase { type: "mcp" url: string - content: string | unknown[] + content: string | McpInstallationMethod[] + parameters?: McpParameter[] } export interface ModeMarketplaceItem extends MarketplaceItemBase { @@ -41,8 +56,7 @@ export interface MarketplaceInstalledMetadata { } export interface MarketplaceFilters { - search?: string type?: string - status?: string + search?: string tags?: string[] } diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index ffbb81e3613..49394b4833c 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -1713,6 +1713,13 @@ export interface OpenSubAgentViewerRequest { title?: string } +// Preview an image attachment in VS Code's built-in image viewer +export interface PreviewImageRequest { + type: "previewImage" + dataUrl: string + filename: string +} + // Set default base branch (webview → extension) export interface SetDefaultBaseBranchRequest { type: "agentManager.setDefaultBaseBranch" @@ -1812,6 +1819,7 @@ export type WebviewMessage = | OpenChangesRequest | RetryConnectionRequest | OpenSubAgentViewerRequest + | PreviewImageRequest | SetDefaultBaseBranchRequest | FetchMarketplaceDataMessage | FilterMarketplaceItemsMessage diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index 4d019f6a7ee..f786c643cc9 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -171,6 +171,7 @@ export const GlobalRoutes = lazy(() => }, }), async (c) => { + Config.global.reset() // kilocode_change - reset cached global config so re-init reads fresh data from disk await Instance.disposeAll() GlobalBus.emit("event", { directory: "global",