mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
feat(marketplace): add MCP servers and modes tabs
Implements the full marketplace with all three tabs functional: - MCP Servers: browse, search, filter by status/tags, install with scope selection, method picker, and parameter form - Modes: browse, search, filter by status/tags, install/remove - Skills: unchanged from previous PR Rewrites marketplace components to use kilo-ui Dialog/Card components, useDialog() for modal management, and vscode.onMessage for result subscriptions. Adds MarketplaceListView shared component for MCP and Modes tabs. Full installer with MCP config writing (local/remote), mode YAML parsing, and template parameter substitution. Adds 18 new i18n keys per locale (16 languages) for MCP/mode specific strings (badges, filters, install method, parameters, type labels).
This commit is contained in:
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -48,4 +48,22 @@ 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",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -45,4 +45,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -41,4 +41,22 @@ 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}} の削除に失敗しました",
|
||||
}
|
||||
|
||||
@@ -41,4 +41,22 @@ 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}} 제거 실패",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}}",
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ 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}} ได้",
|
||||
}
|
||||
|
||||
@@ -40,4 +40,22 @@ 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}} 失败",
|
||||
}
|
||||
|
||||
@@ -40,4 +40,22 @@ 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}} 失敗",
|
||||
}
|
||||
|
||||
@@ -669,8 +669,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
case "removeInstalledMarketplaceItem": {
|
||||
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)
|
||||
const result =
|
||||
message.mpItem.type === "mode"
|
||||
? await this.removeModeViaCli(message.mpItem)
|
||||
: await this.getMarketplace().remove(message.mpItem, scope, workspace)
|
||||
if (result.success && message.mpItem.type !== "mode") await this.disposeCliInstance(scope)
|
||||
this.postMessage({
|
||||
type: "marketplaceRemoveResult",
|
||||
success: result.success,
|
||||
@@ -1256,6 +1259,32 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
await this.fetchAndSendAgents()
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a marketplace mode via the CLI removeAgent endpoint.
|
||||
* Returns a RemoveResult so it plugs into the marketplace removal flow.
|
||||
*/
|
||||
private async removeModeViaCli(item: {
|
||||
id: string
|
||||
content: string
|
||||
}): Promise<{ success: boolean; slug: string; error?: string }> {
|
||||
if (!this.client) return { success: false, slug: item.id, error: "CLI not connected" }
|
||||
const yaml = await import("yaml")
|
||||
const mode = yaml.parse(item.content) as Record<string, unknown> | undefined
|
||||
const slug = (mode?.slug as string) ?? item.id
|
||||
try {
|
||||
const dir = this.getWorkspaceDirectory()
|
||||
const result = await this.client.kilocode.removeAgent({ name: slug, directory: dir })
|
||||
if (result.error) {
|
||||
console.error("[Kilo New] removeAgent (marketplace) returned error:", result.error)
|
||||
return { success: false, slug: item.id, error: String(result.error) }
|
||||
}
|
||||
return { success: true, slug: item.id }
|
||||
} catch (error) {
|
||||
console.error("[Kilo New] Failed to remove mode (marketplace):", error)
|
||||
return { success: false, slug: item.id, error: String(error) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the CLI backend instance so it re-reads config from disk.
|
||||
* Call after any marketplace install/remove that writes config files directly.
|
||||
|
||||
@@ -6,6 +6,9 @@ import { promisify } from "util"
|
||||
import type {
|
||||
MarketplaceItem,
|
||||
SkillMarketplaceItem,
|
||||
McpMarketplaceItem,
|
||||
ModeMarketplaceItem,
|
||||
McpInstallationMethod,
|
||||
InstallMarketplaceItemOptions,
|
||||
InstallResult,
|
||||
RemoveResult,
|
||||
@@ -24,9 +27,96 @@ 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" }
|
||||
}
|
||||
|
||||
const entry = this.buildMcpEntry(content, options.parameters)
|
||||
config.mcp[item.id] = entry
|
||||
|
||||
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
|
||||
|
||||
// Parse the content as JSON — the marketplace API provides JSON config blocks
|
||||
try {
|
||||
return JSON.parse(replaced)
|
||||
} catch {
|
||||
// Fallback: treat as a command string
|
||||
return { command: 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." }
|
||||
}
|
||||
|
||||
// Mode content is a JSON blob for the agent config
|
||||
try {
|
||||
config.agent[item.id] = JSON.parse(item.content)
|
||||
} catch {
|
||||
config.agent[item.id] = { system: item.content }
|
||||
}
|
||||
|
||||
await this.writeConfig(scope, workspace, config)
|
||||
return { success: true, slug: item.id }
|
||||
}
|
||||
|
||||
// ── Skill ───────────────────────────────────────────────────────────
|
||||
|
||||
async installSkill(
|
||||
item: SkillMarketplaceItem,
|
||||
scope: "project" | "global",
|
||||
@@ -106,9 +196,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 +251,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 +286,15 @@ function isSafeId(id: string): boolean {
|
||||
return /^[\w\-@.]+$/.test(id)
|
||||
}
|
||||
|
||||
function substituteParams(template: string, params: Record<string, unknown>): string {
|
||||
let result = template
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
result = result.replaceAll(`{{${key}}}`, String(value ?? ""))
|
||||
result = result.replaceAll(`\${${key}}`, String(value ?? ""))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function findEscapedPaths(dir: string): Promise<string[]> {
|
||||
const resolved = path.resolve(dir)
|
||||
const escaped: string[] = []
|
||||
|
||||
@@ -1,71 +1,109 @@
|
||||
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") => 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 options = (): ScopeOption[] => [
|
||||
{ value: "project", label: t("marketplace.scope.project") },
|
||||
{ value: "global", label: t("marketplace.scope.global") },
|
||||
]
|
||||
const [scope, setScope] = createSignal<ScopeOption>(workspace() ? scopeOptions()[0] : scopeOptions()[1])
|
||||
const initial = workspace() ? options()[0] : options()[1]
|
||||
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)
|
||||
}
|
||||
}
|
||||
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 +111,81 @@ 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) => v && setMethod(v)}
|
||||
/>
|
||||
</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 } 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"
|
||||
|
||||
@@ -16,39 +18,45 @@ interface Props {
|
||||
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 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 link" onClick={() => openLink(props.linkUrl!)}>
|
||||
{name()}
|
||||
</a>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={props.typeBadge}>
|
||||
<Tag>{props.typeBadge}</Tag>
|
||||
</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>
|
||||
<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 +70,18 @@ const ItemCard: Component<Props> = (props) => {
|
||||
}
|
||||
>
|
||||
{scopes().map((scope) => (
|
||||
<Button size="small" class="marketplace-remove-btn" onClick={() => props.onRemove(props.item, scope)}>
|
||||
<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.card.remove")}
|
||||
</Button>
|
||||
))}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ItemCard
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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,
|
||||
ModeMarketplaceItem,
|
||||
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: (McpMarketplaceItem | ModeMarketplaceItem)[]
|
||||
metadata: MarketplaceInstalledMetadata
|
||||
fetching: boolean
|
||||
type: "mcp" | "mode"
|
||||
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 allTags = createMemo(() => {
|
||||
const set = new Set<string>()
|
||||
for (const item of props.items) {
|
||||
for (const tag of item.tags ?? []) set.add(tag)
|
||||
}
|
||||
return Array.from(set).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) => item.tags?.includes(tag))) return false
|
||||
if (!q) return true
|
||||
return (
|
||||
item.id.toLowerCase().includes(q) ||
|
||||
item.name.toLowerCase().includes(q) ||
|
||||
item.description.toLowerCase().includes(q) ||
|
||||
(item.author?.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) => (
|
||||
<ItemCard
|
||||
item={item}
|
||||
metadata={props.metadata}
|
||||
linkUrl={item.type === "mcp" ? (item as McpMarketplaceItem).url : undefined}
|
||||
typeBadge={item.type === "mcp" ? t("marketplace.badge.mcpServer") : t("marketplace.badge.mode")}
|
||||
onInstall={props.onInstall}
|
||||
onRemove={props.onRemove}
|
||||
footer={<For each={item.tags ?? []}>{(tag) => <Tag>{tag}</Tag>}</For>}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,168 +1,184 @@
|
||||
import { Component, createSignal, createMemo, onMount, onCleanup, Show } from "solid-js"
|
||||
import { createSignal, createMemo, createEffect, onCleanup, 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 { SkillsMarketplace } from "./SkillsMarketplace"
|
||||
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 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") {
|
||||
if (msg.success) fetchData()
|
||||
}
|
||||
// Re-fetch when agents or skills change (e.g. after install via CLI)
|
||||
if (msg?.type === "agentsLoaded" || msg?.type === "skillsLoaded") {
|
||||
if (msg.type === "agentsLoaded" || msg.type === "skillsLoaded") {
|
||||
fetchData()
|
||||
}
|
||||
// Re-fetch when workspace directory changes
|
||||
if (msg?.type === "workspaceDirectoryChanged") {
|
||||
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 ?? {} })
|
||||
}
|
||||
|
||||
const handleInstall = (item: MarketplaceItem) => {
|
||||
setInstallItem(item)
|
||||
dialog.show(() => (
|
||||
<InstallModal
|
||||
item={item}
|
||||
onClose={() => dialog.close()}
|
||||
onInstallResult={(success, scope) => {
|
||||
if (success) {
|
||||
telemetry("marketplace_item_installed", {
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
scope,
|
||||
})
|
||||
dialog.close()
|
||||
fetchData()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
const handleRemove = (item: MarketplaceItem, scope: "project" | "global") => {
|
||||
setRemoveItem({ item, scope })
|
||||
dialog.show(() => (
|
||||
<RemoveDialog
|
||||
item={item}
|
||||
scope={scope}
|
||||
onClose={() => dialog.close()}
|
||||
onConfirm={() => {
|
||||
vscode.postMessage({
|
||||
type: "removeInstalledMarketplaceItem",
|
||||
mpItem: item,
|
||||
mpInstallOptions: { target: scope },
|
||||
})
|
||||
telemetry("marketplace_item_removed", {
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
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.mcp")}
|
||||
emptyMessage={t("marketplace.empty.mcp")}
|
||||
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.modes")}
|
||||
emptyMessage={t("marketplace.empty.modes")}
|
||||
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">
|
||||
<SkillsMarketplace
|
||||
items={skills()}
|
||||
metadata={metadata()}
|
||||
fetching={fetching()}
|
||||
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.type.mcp")
|
||||
if (props.item.type === "mode") return t("marketplace.type.mode")
|
||||
return t("marketplace.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,11 +1,11 @@
|
||||
import { Component, createSignal, createMemo, For, Show } from "solid-js"
|
||||
import { 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"
|
||||
import { ItemCard } from "./ItemCard"
|
||||
|
||||
interface Props {
|
||||
items: SkillMarketplaceItem[]
|
||||
@@ -17,7 +17,7 @@ interface Props {
|
||||
|
||||
const ALL = "__all__"
|
||||
|
||||
const SkillsMarketplace: Component<Props> = (props) => {
|
||||
export const SkillsMarketplace = (props: Props) => {
|
||||
const { t } = useLanguage()
|
||||
const [search, setSearch] = createSignal("")
|
||||
const [category, setCategory] = createSignal(ALL)
|
||||
@@ -82,5 +82,3 @@ const SkillsMarketplace: Component<Props> = (props) => {
|
||||
</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,10 +36,52 @@
|
||||
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 {
|
||||
@@ -48,9 +103,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,14 +123,6 @@
|
||||
color: var(--text-base);
|
||||
}
|
||||
|
||||
.marketplace-card-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.marketplace-card-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.marketplace-card-author {
|
||||
font-size: 12px;
|
||||
color: var(--text-weak);
|
||||
@@ -115,117 +159,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
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user