Merge pull request #7133 from Kilo-Org/mark/marketplace-full-implementation

feat(marketplace): add MCP servers and modes tabs
This commit is contained in:
Mark IJbema
2026-03-17 17:37:23 +01:00
committed by GitHub
35 changed files with 1368 additions and 460 deletions
+23 -2
View File
@@ -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": "تجاهل",
}
+23 -2
View File
@@ -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",
}
+23 -2
View File
@@ -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",
}
+23 -2
View File
@@ -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",
}
+23 -2
View File
@@ -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",
}
+23 -2
View File
@@ -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",
}
+23 -2
View File
@@ -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",
}
+23 -2
View File
@@ -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",
}
+23 -2
View File
@@ -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": "閉じる",
}
+23 -2
View File
@@ -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": "닫기",
}
+23 -2
View File
@@ -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",
}
+23 -2
View File
@@ -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ć",
}
+23 -2
View File
@@ -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": "Закрыть",
}
+23 -2
View File
@@ -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": "ปิด",
}
+50
View File
@@ -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",
}
+23 -2
View File
@@ -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": "关闭",
}
+23 -2
View File
@@ -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": "關閉",
}
+40 -19
View File
@@ -671,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,
@@ -684,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,
@@ -1250,22 +1258,34 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
*/
private async handleRemoveMode(name: string): Promise<void> {
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()
}
@@ -1281,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)
})
}
/**
@@ -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<InstallResult> {
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<InstallResult> {
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<string, unknown>): Record<string, unknown> {
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<InstallResult> {
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<RemoveResult> {
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<RemoveResult> {
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<RemoveResult> {
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<Record<string, Record<string, unknown>>> {
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<string, unknown>,
): Promise<void> {
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<string, string> = {
read: "read",
edit: "edit",
browser: "bash",
command: "bash",
mcp: "mcp",
}
const ALL_PERMISSIONS = ["read", "edit", "bash", "mcp"]
function convertModeToAgent(content: string): Record<string, unknown> {
const mode = yaml.parse(content) as Record<string, unknown>
const groups = (mode.groups ?? []) as Array<string | [string, Record<string, unknown>]>
const permission: Record<string, unknown> = {}
const allowed = new Set<string>()
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, unknown>): 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<string[]> {
const resolved = path.resolve(dir)
const escaped: string[] = []
@@ -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",
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7bc9274e748f94e0ae979f688a87c52b9a3d579ac015a2a49f327f8c96d002db
size 11116
oid sha256:0d5400d9db070953fcbf4cc3f8a4968b80c5bccb1a67ee63a463aa0539de1f41
size 11190
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:be90b297cbdef33fa1c3c67be7a42e4c00a5f3d68147a5fbf54a04ab7b261d04
size 52330
oid sha256:782c1fae4bed19825b56570e573d09cc80aa2180ca5c6a4c495867f207d95beb
size 54728
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d27dc2c85a377f7daab0b7de444a7d883a4503ab45c92c0c0a9155de89e2a221
size 49775
oid sha256:3d309302b177f97f198a2b889c96d08cf4578d219043d48a9d0251460be411c4
size 52039
@@ -1,71 +1,119 @@
import { Component, createSignal, onMount, onCleanup, Show, For } from "solid-js"
import { createSignal, createEffect, onCleanup, Show, For } from "solid-js"
import { Dialog } from "@kilocode/kilo-ui/dialog"
import { Button } from "@kilocode/kilo-ui/button"
import { RadioGroup } from "@kilocode/kilo-ui/radio-group"
import { Select } from "@kilocode/kilo-ui/select"
import { TextField } from "@kilocode/kilo-ui/text-field"
import { Spinner } from "@kilocode/kilo-ui/spinner"
import { useVSCode } from "../../context/vscode"
import { useServer } from "../../context/server"
import { useLanguage } from "../../context/language"
import type { MarketplaceItem } from "../../types/marketplace"
import type { MarketplaceItem, McpMarketplaceItem, McpInstallationMethod, McpParameter } from "../../types/marketplace"
interface ScopeOption {
value: "project" | "global"
label: string
disabled?: boolean
}
interface Props {
item: MarketplaceItem
onClose: () => void
onInstallResult?: (success: boolean) => void
onInstallResult: (
success: boolean,
scope: "project" | "global",
extra?: { hasParameters?: boolean; installationMethodName?: string },
) => void
}
const InstallModal: Component<Props> = (props) => {
export const InstallModal = (props: Props) => {
const vscode = useVSCode()
const server = useServer()
const { t } = useLanguage()
const workspace = () => server.workspaceDirectory()
const scopeOptions = (): ScopeOption[] => [
{ value: "project", label: t("marketplace.install.scope.project"), disabled: !workspace() },
{ value: "global", label: t("marketplace.install.scope.global") },
]
const [scope, setScope] = createSignal<ScopeOption>(workspace() ? scopeOptions()[0] : scopeOptions()[1])
const options = (): ScopeOption[] =>
workspace()
? [
{ value: "project", label: t("marketplace.scope.project") },
{ value: "global", label: t("marketplace.scope.global") },
]
: [{ value: "global", label: t("marketplace.scope.global") }]
const initial = workspace() ? options()[0] : options()[0]
const [scope, setScope] = createSignal<ScopeOption>(initial)
const [installing, setInstalling] = createSignal(false)
const [result, setResult] = createSignal<{ success: boolean; error?: string } | null>(null)
const [params, setParams] = createSignal<Record<string, string>>({})
const prerequisites = () => props.item.prerequisites ?? []
// MCP installation methods
const methods = (): McpInstallationMethod[] => {
if (props.item.type !== "mcp") return []
const mcp = props.item as McpMarketplaceItem
if (!Array.isArray(mcp.content)) return []
return mcp.content
}
onMount(() => {
const handler = (event: MessageEvent) => {
const msg = event.data
if (msg?.type === "marketplaceInstallResult" && msg.slug === props.item.id) {
const [method, setMethod] = createSignal<McpInstallationMethod | undefined>(methods()[0])
const prerequisites = (): string[] => {
if (method()?.prerequisites?.length) return method()!.prerequisites!
return props.item.prerequisites ?? []
}
const parameters = (): McpParameter[] => {
if (method()?.parameters?.length) return method()!.parameters!
if (props.item.type === "mcp") return (props.item as McpMarketplaceItem).parameters ?? []
return []
}
const valid = () => {
for (const param of parameters()) {
if (!param.optional && !params()[param.key]?.trim()) return false
}
return true
}
const setParam = (key: string, value: string) => {
setParams((prev) => ({ ...prev, [key]: value }))
}
createEffect(() => {
const unsub = vscode.onMessage((msg) => {
if (msg.type === "marketplaceInstallResult" && msg.slug === props.item.id) {
setInstalling(false)
setResult({ success: msg.success, error: msg.error })
props.onInstallResult?.(msg.success)
props.onInstallResult(msg.success, scope().value, {
hasParameters: Object.keys(params()).length > 0,
installationMethodName: method()?.name,
})
}
}
window.addEventListener("message", handler)
onCleanup(() => window.removeEventListener("message", handler))
})
onCleanup(unsub)
})
const handleInstall = () => {
setInstalling(true)
const paramValues: Record<string, unknown> = { ...params() }
if (method()) {
paramValues.__method = method()!.name
}
vscode.postMessage({
type: "installMarketplaceItem",
mpItem: props.item,
mpInstallOptions: { target: scope().value },
mpInstallOptions: {
target: scope().value,
parameters: Object.keys(paramValues).length > 0 ? paramValues : undefined,
},
})
}
return (
<div class="marketplace-modal-overlay" onClick={() => !installing() && props.onClose()}>
<div class="marketplace-modal" onClick={(e) => e.stopPropagation()}>
<h3>{t("marketplace.install.title", { name: props.item.name })}</h3>
<Show when={!result()}>
<div class="marketplace-modal-section">
<label>{t("marketplace.install.scope")}</label>
<Dialog title={t("marketplace.install.title", { name: props.item.name })} fit>
<Show when={!result()}>
<div class="install-modal-body">
<div class="install-modal-section">
<span class="install-modal-label">{t("marketplace.install.scope")}</span>
<RadioGroup
options={scopeOptions()}
options={options()}
current={scope()}
value={(x: ScopeOption) => x.value}
label={(x: ScopeOption) => x.label}
@@ -73,46 +121,86 @@ const InstallModal: Component<Props> = (props) => {
/>
</div>
<Show when={methods().length > 1}>
<div class="install-modal-section">
<span class="install-modal-label">{t("marketplace.install.method")}</span>
<Select
options={methods()}
current={method()}
value={(m: McpInstallationMethod) => m.name}
label={(m: McpInstallationMethod) => m.name}
onSelect={(v: McpInstallationMethod | undefined) => {
if (v) {
setMethod(v)
setParams({})
}
}}
/>
</div>
</Show>
<Show when={prerequisites().length > 0}>
<div class="marketplace-modal-section">
<label>{t("marketplace.install.prerequisites")}</label>
<ul class="marketplace-prerequisites">
<div class="install-modal-section">
<span class="install-modal-label">{t("marketplace.install.prerequisites")}</span>
<ul class="install-modal-prerequisites">
<For each={prerequisites()}>{(p) => <li>{p}</li>}</For>
</ul>
</div>
</Show>
<div class="marketplace-modal-actions">
<Show when={parameters().length > 0}>
<div class="install-modal-section">
<span class="install-modal-label">{t("marketplace.install.parameters")}</span>
<For each={parameters()}>
{(param) => (
<div class="install-modal-param">
<TextField
label={param.name + (param.optional ? ` (${t("marketplace.install.optional")})` : "")}
placeholder={param.placeholder ?? ""}
value={params()[param.key] ?? ""}
onChange={(v: string) => setParam(param.key, v)}
/>
</div>
)}
</For>
</div>
</Show>
<div class="install-modal-footer">
<Button variant="secondary" onClick={props.onClose} disabled={installing()}>
{t("marketplace.install.cancel")}
</Button>
<Button variant="primary" onClick={handleInstall} disabled={installing()}>
{installing() ? t("marketplace.install.installing") : t("marketplace.card.install")}
<Button variant="primary" onClick={handleInstall} disabled={installing() || !valid()}>
<Show when={installing()} fallback={t("marketplace.install")}>
<Spinner /> {t("marketplace.install.installing")}
</Show>
</Button>
</div>
</Show>
</div>
</Show>
<Show when={result()}>
{(r) => (
<div class="marketplace-modal-result">
<Show
when={r().success}
fallback={
<>
<p class="marketplace-error">{r().error ?? t("marketplace.install.failed")}</p>
<Show when={result()}>
{(r) => (
<div class="install-modal-result">
<Show
when={r().success}
fallback={
<>
<p class="install-modal-error-msg">{r().error ?? t("marketplace.install.failed")}</p>
<div class="install-modal-footer">
<Button onClick={props.onClose}>{t("marketplace.install.close")}</Button>
</>
}
>
<p class="marketplace-success">{t("marketplace.install.success")}</p>
</div>
</>
}
>
<p class="install-modal-success">{t("marketplace.install.success")}</p>
<div class="install-modal-footer">
<Button onClick={props.onClose}>{t("marketplace.install.done")}</Button>
</Show>
</div>
)}
</Show>
</div>
</div>
</div>
</Show>
</div>
)}
</Show>
</Dialog>
)
}
export default InstallModal
@@ -1,7 +1,9 @@
import { Component, Show, JSX } from "solid-js"
import { Show, JSX, createSignal, onMount } from "solid-js"
import { Button } from "@kilocode/kilo-ui/button"
import { Card } from "@kilocode/kilo-ui/card"
import { Tag } from "@kilocode/kilo-ui/tag"
import type { MarketplaceItem, MarketplaceInstalledMetadata } from "../../types/marketplace"
import { useVSCode } from "../../context/vscode"
import { useLanguage } from "../../context/language"
import { installedScopes } from "./utils"
@@ -10,45 +12,61 @@ interface Props {
metadata: MarketplaceInstalledMetadata
displayName?: string
linkUrl?: string
typeBadge?: string
onInstall: (item: MarketplaceItem) => void
onRemove: (item: MarketplaceItem, scope: "project" | "global") => void
footer?: JSX.Element
}
const ItemCard: Component<Props> = (props) => {
export const ItemCard = (props: Props) => {
const vscode = useVSCode()
const { t } = useLanguage()
const scopes = () => installedScopes(props.item.id, props.item.type, props.metadata)
const installed = () => scopes().length > 0
const name = () => props.displayName ?? props.item.name
const [expanded, setExpanded] = createSignal(false)
const [clamped, setClamped] = createSignal(false)
let ref: HTMLParagraphElement | undefined
onMount(() => {
if (ref && ref.scrollHeight > ref.clientHeight) setClamped(true)
})
const openLink = (url: string) => {
vscode.postMessage({ type: "openExternal", url })
}
return (
<div class="marketplace-card">
<Card class="marketplace-card">
<div class="marketplace-card-header">
<div class="marketplace-card-title">
<Show when={props.linkUrl} fallback={<span class="marketplace-card-name">{name()}</span>}>
<a
class="marketplace-card-name marketplace-card-link"
href={props.linkUrl}
target="_blank"
rel="noopener noreferrer"
>
<span class="marketplace-card-name clickable" onClick={() => openLink(props.linkUrl!)}>
{name()}
</a>
</Show>
<Show when={props.typeBadge}>
<Tag>{props.typeBadge}</Tag>
</span>
</Show>
</div>
<Show when={props.item.author}>
<span class="marketplace-card-author">{props.item.author}</span>
<span class="marketplace-card-author">
<Show when={props.item.authorUrl} fallback={t("marketplace.card.by", { author: props.item.author! })}>
<span class="link" onClick={() => openLink(props.item.authorUrl!)}>
{t("marketplace.card.by", { author: props.item.author! })}
</span>
</Show>
</span>
</Show>
</div>
<p class="marketplace-card-description">{props.item.description}</p>
<p ref={ref} class="marketplace-card-description" classList={{ expanded: expanded() }}>
{props.item.description}
</p>
<Show when={clamped()}>
<button class="marketplace-card-expand" onClick={() => setExpanded(!expanded())}>
{expanded() ? t("marketplace.card.showLess") : t("marketplace.card.showMore")}
</button>
</Show>
<div class="marketplace-card-footer">
<div class="marketplace-card-tags">
<Show when={installed()}>
<Tag class="marketplace-installed-tag">{t("marketplace.card.installed")}</Tag>
<Tag class="marketplace-badge-installed">{t("marketplace.card.installed")}</Tag>
</Show>
{props.footer}
</div>
@@ -62,15 +80,20 @@ const ItemCard: Component<Props> = (props) => {
}
>
{scopes().map((scope) => (
<Button size="small" class="marketplace-remove-btn" onClick={() => props.onRemove(props.item, scope)}>
{scopes().length > 1 ? t("marketplace.card.removeScope", { scope }) : t("marketplace.card.remove")}
<Button
size="small"
variant="ghost"
class="marketplace-remove-btn"
onClick={() => props.onRemove(props.item, scope)}
>
{scopes().length > 1
? t("marketplace.card.removeScope", { scope: t(`marketplace.scope.${scope}`) })
: t("marketplace.card.remove")}
</Button>
))}
</Show>
</div>
</div>
</div>
</Card>
)
}
export default ItemCard
@@ -0,0 +1,151 @@
import { createSignal, createMemo, For, Show } from "solid-js"
import { TextField } from "@kilocode/kilo-ui/text-field"
import { Select } from "@kilocode/kilo-ui/select"
import { Tag } from "@kilocode/kilo-ui/tag"
import { Spinner } from "@kilocode/kilo-ui/spinner"
import type {
MarketplaceItem,
McpMarketplaceItem,
SkillMarketplaceItem,
MarketplaceInstalledMetadata,
} from "../../types/marketplace"
import { useLanguage } from "../../context/language"
import { isInstalled } from "./utils"
import { ItemCard } from "./ItemCard"
interface StatusOption {
value: string
label: string
}
interface Props {
items: MarketplaceItem[]
metadata: MarketplaceInstalledMetadata
fetching: boolean
type: "mcp" | "mode" | "skill"
searchPlaceholder: string
emptyMessage: string
onInstall: (item: MarketplaceItem) => void
onRemove: (item: MarketplaceItem, scope: "project" | "global") => void
}
export const MarketplaceListView = (props: Props) => {
const { t } = useLanguage()
const [search, setSearch] = createSignal("")
const [status, setStatus] = createSignal<StatusOption>({ value: "all", label: t("marketplace.filter.all") })
const [tags, setTags] = createSignal<string[]>([])
const options = (): StatusOption[] => [
{ value: "all", label: t("marketplace.filter.all") },
{ value: "installed", label: t("marketplace.filter.installed") },
{ value: "notInstalled", label: t("marketplace.filter.notInstalled") },
]
const tagsFor = (item: MarketplaceItem): string[] => {
if (item.type === "skill") return [(item as SkillMarketplaceItem).displayCategory]
return item.tags ?? []
}
const allTags = createMemo(() => {
const counts = new Map<string, number>()
for (const item of props.items) {
for (const tag of tagsFor(item)) counts.set(tag, (counts.get(tag) ?? 0) + 1)
}
const min = props.type === "mcp" ? 5 : 1
return Array.from(counts.entries())
.filter(([, n]) => n >= min)
.map(([tag]) => tag)
.sort()
})
const toggleTag = (tag: string) => {
const current = tags()
if (current.includes(tag)) {
setTags(current.filter((t) => t !== tag))
} else {
setTags([...current, tag])
}
}
const filtered = createMemo(() => {
const q = search().toLowerCase()
const s = status().value
const active = tags()
return props.items.filter((item) => {
if (s === "installed" && !isInstalled(item.id, item.type, props.metadata)) return false
if (s === "notInstalled" && isInstalled(item.id, item.type, props.metadata)) return false
if (active.length > 0 && !active.some((tag) => tagsFor(item).includes(tag))) return false
if (!q) return true
const skill = item.type === "skill" ? (item as SkillMarketplaceItem) : undefined
return (
item.id.toLowerCase().includes(q) ||
item.name.toLowerCase().includes(q) ||
item.description.toLowerCase().includes(q) ||
(item.author?.toLowerCase().includes(q) ?? false) ||
(skill?.displayName.toLowerCase().includes(q) ?? false)
)
})
})
return (
<div class="marketplace-list">
<div class="marketplace-filters">
<div class="marketplace-search-field">
<TextField placeholder={props.searchPlaceholder} value={search()} onChange={setSearch} />
</div>
<Select
options={options()}
current={status()}
value={(o: StatusOption) => o.value}
label={(o: StatusOption) => o.label}
onSelect={(v: StatusOption | undefined) => v && setStatus(v)}
/>
</div>
<Show when={allTags().length > 0}>
<div class="marketplace-active-tags">
<For each={allTags()}>
{(tag) => (
<button
class="marketplace-tag-filter"
classList={{ active: tags().includes(tag) }}
onClick={() => toggleTag(tag)}
>
<Tag>{tag}</Tag>
</button>
)}
</For>
</div>
</Show>
<Show
when={!props.fetching}
fallback={
<div class="marketplace-loading">
<Spinner />
</div>
}
>
<Show when={filtered().length > 0} fallback={<p class="marketplace-empty">{props.emptyMessage}</p>}>
<div class="marketplace-grid">
<For each={filtered()}>
{(item) => {
const skill = item.type === "skill" ? (item as SkillMarketplaceItem) : undefined
const mcp = item.type === "mcp" ? (item as McpMarketplaceItem) : undefined
return (
<ItemCard
item={item}
metadata={props.metadata}
displayName={skill?.displayName}
linkUrl={skill?.githubUrl ?? mcp?.url}
onInstall={props.onInstall}
onRemove={props.onRemove}
footer={<For each={tagsFor(item)}>{(tag) => <Tag>{tag}</Tag>}</For>}
/>
)
}}
</For>
</div>
</Show>
</Show>
</div>
)
}
@@ -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<MarketplaceItem[]>([])
const [metadata, setMetadata] = createSignal<MarketplaceInstalledMetadata>(EMPTY_METADATA)
const [fetching, setFetching] = createSignal(true)
const [errors, setErrors] = createSignal<string[]>([])
const [tab, setTab] = createSignal("skill")
// Install/Remove state
const [installItem, setInstallItem] = createSignal<MarketplaceItem | null>(null)
const [removeItem, setRemoveItem] = createSignal<{ item: MarketplaceItem; scope: "project" | "global" } | null>(null)
const [pendingRemove, setPendingRemove] = createSignal<string | null>(null)
const [removeError, setRemoveError] = createSignal<string | null>(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<string, unknown>) => {
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(() => (
<InstallModal
item={item}
onClose={() => 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(() => (
<RemoveDialog
item={item}
scope={scope}
onClose={() => 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 (
<div class="marketplace-view">
<Tabs value={tab()} onChange={setTab}>
<Show when={errors().length > 0}>
{errors().map((err, idx) => (
<Card variant="error" class="marketplace-error-banner">
<span>{err}</span>
<Button variant="ghost" size="small" onClick={() => dismissError(idx)}>
{t("marketplace.error.dismiss")}
</Button>
</Card>
))}
</Show>
<Tabs value={tab()} onChange={setTab} class="marketplace-tabs-root">
<Tabs.List>
<Tabs.Trigger value="skill">{t("marketplace.tab.skills")}</Tabs.Trigger>
<Tabs.Trigger value="mcp">{t("marketplace.tab.mcpServers")}</Tabs.Trigger>
<Tabs.Trigger value="mcp">{t("marketplace.tab.mcp")}</Tabs.Trigger>
<Tabs.Trigger value="mode">{t("marketplace.tab.modes")}</Tabs.Trigger>
<Tabs.Trigger value="skill">{t("marketplace.tab.skills")}</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="skill">
<SkillsMarketplace
items={skills()}
metadata={metadata()}
fetching={fetching()}
onInstall={handleInstall}
onRemove={handleRemove}
/>
</Tabs.Content>
<div class="marketplace-content">
<Tabs.Content value="mcp">
<MarketplaceListView
items={mcps()}
metadata={metadata()}
fetching={fetching()}
type="mcp"
searchPlaceholder={t("marketplace.search")}
emptyMessage={t("marketplace.empty")}
onInstall={handleInstall}
onRemove={handleRemove}
/>
</Tabs.Content>
<Tabs.Content value="mcp">
<div class="marketplace-placeholder">
<p>{t("marketplace.placeholder")}</p>
</div>
</Tabs.Content>
<Tabs.Content value="mode">
<MarketplaceListView
items={modes()}
metadata={metadata()}
fetching={fetching()}
type="mode"
searchPlaceholder={t("marketplace.search")}
emptyMessage={t("marketplace.empty")}
onInstall={handleInstall}
onRemove={handleRemove}
/>
</Tabs.Content>
<Tabs.Content value="mode">
<div class="marketplace-placeholder">
<p>{t("marketplace.placeholder")}</p>
</div>
</Tabs.Content>
<Tabs.Content value="skill">
<MarketplaceListView
items={skills()}
metadata={metadata()}
fetching={fetching()}
type="skill"
searchPlaceholder={t("marketplace.search")}
emptyMessage={t("marketplace.empty")}
onInstall={handleInstall}
onRemove={handleRemove}
/>
</Tabs.Content>
</div>
</Tabs>
<Show when={errors().length > 0}>
<div class="marketplace-errors">
{errors().map((e) => (
<p class="marketplace-error">{e}</p>
))}
</div>
</Show>
<Show when={removeError()}>
<div class="marketplace-errors">
<p class="marketplace-error">{removeError()}</p>
</div>
</Show>
<Show when={installItem()}>
{(item) => (
<InstallModal
item={item()}
onClose={() => setInstallItem(null)}
onInstallResult={(success) => {
if (success) {
setInstallItem(null)
fetchData()
}
}}
/>
)}
</Show>
<Show when={removeItem()}>
{(r) => (
<RemoveDialog
item={r().item}
scope={r().scope}
onClose={() => setRemoveItem(null)}
onConfirm={confirmRemove}
/>
)}
</Show>
</div>
)
}
export default MarketplaceView
@@ -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> = (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 (
<div class="marketplace-modal-overlay" onClick={props.onClose}>
<div class="marketplace-modal" onClick={(e) => e.stopPropagation()}>
<h3>{t("marketplace.remove.title", { name: props.item.name })}</h3>
<p>{t("marketplace.remove.confirm", { type: props.item.type, scope: props.scope })}</p>
<div class="marketplace-modal-actions">
<Button variant="secondary" onClick={props.onClose}>
{t("marketplace.remove.cancel")}
</Button>
<Button variant="primary" class="marketplace-remove-btn" onClick={props.onConfirm}>
{t("marketplace.remove.confirm.button")}
</Button>
</div>
<Dialog title={t("marketplace.remove.title", { name: props.item.name })} fit>
<p>{t("marketplace.remove.confirm", { type: typeName(), scope: t(`marketplace.scope.${props.scope}`) })}</p>
<div class="marketplace-remove-actions">
<Button variant="secondary" onClick={props.onClose}>
{t("marketplace.remove.cancel")}
</Button>
<Button variant="primary" class="danger-btn" onClick={props.onConfirm}>
{t("marketplace.remove.confirm.button")}
</Button>
</div>
</div>
</Dialog>
)
}
export default RemoveDialog
@@ -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> = (props) => {
const { t } = useLanguage()
const [search, setSearch] = createSignal("")
const [category, setCategory] = createSignal(ALL)
const categories = createMemo(() => {
const set = new Set<string>()
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 (
<div class="marketplace-list">
<div class="marketplace-filters">
<TextField placeholder={t("marketplace.search.skills")} value={search()} onChange={setSearch} />
</div>
<div class="marketplace-category-filters">
<RadioGroup
options={categories()}
current={category()}
value={(c: string) => c}
label={(c: string) => (c === ALL ? t("marketplace.category.all") : c)}
onSelect={(v: string | undefined) => v && setCategory(v)}
/>
</div>
<Show when={!props.fetching} fallback={<Spinner />}>
<Show when={filtered().length > 0} fallback={<p class="marketplace-empty">{t("marketplace.empty.skills")}</p>}>
<div class="marketplace-grid">
<For each={filtered()}>
{(item) => (
<ItemCard
item={item}
metadata={props.metadata}
displayName={item.displayName}
linkUrl={item.githubUrl}
onInstall={props.onInstall}
onRemove={props.onRemove}
footer={<Tag>{item.displayCategory}</Tag>}
/>
)}
</For>
</div>
</Show>
</Show>
</div>
)
}
export default SkillsMarketplace
@@ -1 +1 @@
export { default as MarketplaceView } from "./MarketplaceView"
export { MarketplaceView } from "./MarketplaceView"
@@ -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;
}
@@ -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
@@ -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: () => (
<StoryProviders>
<div style={{ width: "420px", height: "700px", overflow: "auto", padding: "12px" }}>
<SkillsMarketplace
<MarketplaceListView
items={MOCK_SKILLS}
metadata={EMPTY_METADATA}
fetching={false}
type="skill"
searchPlaceholder="Search skills..."
emptyMessage="No skills found"
onInstall={noop}
onRemove={noop}
/>
@@ -133,10 +136,13 @@ export const SkillsTabWithInstalled: Story = {
render: () => (
<StoryProviders>
<div style={{ width: "420px", height: "700px", overflow: "auto", padding: "12px" }}>
<SkillsMarketplace
<MarketplaceListView
items={MOCK_SKILLS}
metadata={PARTIAL_INSTALLED}
fetching={false}
type="skill"
searchPlaceholder="Search skills..."
emptyMessage="No skills found"
onInstall={noop}
onRemove={noop}
/>
@@ -150,7 +156,16 @@ export const SkillsTabEmpty: Story = {
render: () => (
<StoryProviders>
<div style={{ width: "420px", height: "400px", overflow: "auto", padding: "12px" }}>
<SkillsMarketplace items={[]} metadata={EMPTY_METADATA} fetching={false} onInstall={noop} onRemove={noop} />
<MarketplaceListView
items={[]}
metadata={EMPTY_METADATA}
fetching={false}
type="skill"
searchPlaceholder="Search skills..."
emptyMessage="No skills found"
onInstall={noop}
onRemove={noop}
/>
</div>
</StoryProviders>
),
@@ -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[]
}
@@ -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",