UNPKG

@oglabs/mcp

Version:

Interactive CLI tool and MCP server for managing MCP configurations with tech stack detection and recommendations

1 lines 57.6 kB
!function(){const e=document.createElement("link").relList;if(!(e&&e.supports&&e.supports("modulepreload"))){for(const e of document.querySelectorAll('link[rel="modulepreload"]'))t(e);new MutationObserver(e=>{for(const n of e)if("childList"===n.type)for(const e of n.addedNodes)"LINK"===e.tagName&&"modulepreload"===e.rel&&t(e)}).observe(document,{childList:!0,subtree:!0})}function t(e){if(e.ep)return;e.ep=!0;const t=function(e){const t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),"use-credentials"===e.crossOrigin?t.credentials="include":"anonymous"===e.crossOrigin?t.credentials="omit":t.credentials="same-origin",t}(e);fetch(e.href,t)}}();class e{constructor(){this.debounceTimer=null,this.isSearching=!1,this.searchAbortController=null,this.searchInput=null,this.loadingIndicator=null,this.searchHistory=new Map,this.maxHistorySize=50,this.searchMetrics={totalSearches:0,averageSearchTime:0,cacheHits:0,cacheMisses:0},this.config={debounceDelay:300,loadingThreshold:100,cancelPreviousSearch:!0},this.init()}init(){this.searchInput=document.getElementById("searchInput"),this.loadingIndicator=document.getElementById("searchLoadingIndicator"),this.searchInput&&(this.searchInput.addEventListener("input",this.handleSearchInput.bind(this)),this.searchInput.addEventListener("keydown",this.handleKeydown.bind(this)),document.addEventListener("keydown",e=>{"Escape"===e.key&&this.searchInput===document.activeElement&&this.clearSearch()}))}handleSearchInput(e){const t=e.target.value.trim();this.debounceTimer&&clearTimeout(this.debounceTimer),this.config.cancelPreviousSearch&&this.searchAbortController&&this.searchAbortController.abort(),this.debounceTimer=setTimeout(()=>{this.performSearch(t)},this.config.debounceDelay)}handleKeydown(e){"Enter"===e.key&&(e.preventDefault(),this.debounceTimer&&clearTimeout(this.debounceTimer),this.performSearch(this.searchInput.value.trim()))}async performSearch(e){const t=performance.now(),n=this.buildCacheKey(e);if(this.searchHistory.has(n)){const e=this.searchHistory.get(n);return this.searchMetrics.cacheHits++,this.applySearchResults(e),void this.updatePerformanceMetrics(t)}this.searchMetrics.cacheMisses++,this.searchAbortController=new AbortController;try{const o=setTimeout(()=>{this.showLoading()},this.config.loadingThreshold);this.isSearching=!0;const a=await this.executeSearch(e,this.searchAbortController.signal);clearTimeout(o),this.cacheSearchResults(n,a),this.applySearchResults(a),this.updatePerformanceMetrics(t)}catch(o){o.name}finally{this.isSearching=!1,this.hideLoading(),this.searchAbortController=null}}async executeSearch(e,t){return new Promise(async(n,o)=>{if(t.aborted)o(new DOMException("Search cancelled","AbortError"));else try{if("function"==typeof window.searchServers){const t=window.searchServers(e);t&&"function"==typeof t.then&&await t,n({searchTerm:e,timestamp:Date.now()})}else o(new Error("Search function not available"))}catch(a){o(a)}})}buildCacheKey(e){var t,n,o;const a=(null==(t=document.getElementById("sortBySelect"))?void 0:t.value)||"a-z",r=(null==(n=document.getElementById("groupBySelect"))?void 0:n.value)||"none",s=(null==(o=document.getElementById("starsFilterSelect"))?void 0:o.value)||"0";return`${e.toLowerCase()}|${a}|${r}|${s}`}cacheSearchResults(e,t){if(this.searchHistory.size>=this.maxHistorySize){const e=this.searchHistory.keys().next().value;this.searchHistory.delete(e)}this.searchHistory.set(e,t)}applySearchResults(e){}showLoading(){this.loadingIndicator&&(this.loadingIndicator.style.display="block")}hideLoading(){this.loadingIndicator&&(this.loadingIndicator.style.display="none")}clearSearch(){this.searchInput&&(this.searchInput.value="",this.performSearch(""))}updatePerformanceMetrics(e){const t=performance.now()-e;this.searchMetrics.totalSearches++;const n=this.searchMetrics.averageSearchTime,o=this.searchMetrics.totalSearches;this.searchMetrics.averageSearchTime=(n*(o-1)+t)/o}getPerformanceMetrics(){return{...this.searchMetrics,cacheHitRate:this.searchMetrics.cacheHits/(this.searchMetrics.cacheHits+this.searchMetrics.cacheMisses),cacheSize:this.searchHistory.size}}clearCache(){this.searchHistory.clear(),this.searchMetrics.cacheHits=0,this.searchMetrics.cacheMisses=0}invalidateCache(){this.searchHistory.clear()}}let t;"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{t=new e,window.searchPerformanceManager=t}):(t=new e,window.searchPerformanceManager=t);let n={mcpServers:{}},o={},a={},r=-1,s=[],i="none",c="a-z",l=0;function d(e){n=e,"undefined"!=typeof window&&window.invalidateInstalledServersCache&&window.invalidateInstalledServersCache()}function u(e){a=e}function p(e){r=e}function m(e){l=e}let g=null,h=null;function v(e,t){const n=document.getElementById("message");n.textContent=e,n.className=`message ${t}`,n.style.display="block",setTimeout(()=>{n.style.display="none"},5e3)}function y(){document.getElementById("serverModal").style.display="none"}function f(e,t){if(!n||!n.mcpServers)return!1;const o=(a=n)&&a.mcpServers?JSON.stringify(Object.keys(a.mcpServers).sort()):"";var a;if(null!==g&&h===o||(g=function(){if(!n||!n.mcpServers)return{byKey:new Map,byCommand:new Map,byNormalizedCommand:new Map};const e=new Map,t=new Map,o=new Map;return Object.entries(n.mcpServers).forEach(([n,a])=>{e.set(n,n);const r=a.command+" "+a.args.join(" ");t.set(r,n);const s=(i=r)?i.trim().replace(/\s+/g," "):"";var i;o.set(s,n)}),{byKey:e,byCommand:t,byNormalizedCommand:o}}(),h=o),g.byKey.has(e))return!0;if(g.byCommand.has(t.installCommand))return!0;const r=(s=t.installCommand)?s.trim().replace(/\s+/g," "):"";var s;return!!g.byNormalizedCommand.has(r)}function w(){g=null,h=null}async function b(){try{const e=await fetch("/api/servers");!function(e){o=e}(await e.json())}catch(e){v("Failed to load servers","error")}}function S(e=o){const t=document.getElementById("serverGrid");t.innerHTML="","category"===i?function(e,t){const n={},o=Object.entries(e).map(([e,t])=>({key:e,server:t}));o.forEach(({key:e,server:t})=>{const o=t.category||"Other";n[o]||(n[o]=[]),n[o].push({key:e,server:t})});Object.keys(n).sort().forEach(e=>{const o=document.createElement("div");o.className="category-section collapsed",o.setAttribute("data-category",e),o.innerHTML=`\n <h2 class="category-title accordion-header" onclick="toggleCategory('${e}')">\n <span class="accordion-icon">▶</span>\n ${e} \n <span class="category-count">(${n[e].length})</span>\n </h2>\n <div class="category-grid accordion-content" data-category="${e}" style="display: none;">\n `,n[e].forEach(({key:e,server:t})=>{const n=E(e,t);n&&o.querySelector(".category-grid").appendChild(n)}),o.innerHTML+="</div>",t.appendChild(o)})}(e,t):function(e,t){const n=document.createElement("div");n.className="category-grid",n.style.marginTop="0";const o=Object.entries(e).map(([e,t])=>({key:e,server:t}));o.forEach(({key:e,server:t})=>{const o=E(e,t);o&&n.appendChild(o)}),t.appendChild(n)}(e,t),setTimeout(()=>{!function(){try{JSON.parse(localStorage.getItem("expandedCategories")||"[]").forEach(e=>{var t;const n=document.querySelector(`.category-section[data-category="${e}"]`)||(null==(t=document.querySelector(`.accordion-content[data-category="${e}"]`))?void 0:t.parentElement);if(n){const e=n.querySelector(".accordion-content"),t=n.querySelector(".accordion-icon");e&&t&&(e.style.display="grid",t.textContent="▼",n.classList.remove("collapsed"))}})}catch(e){}}(),setTimeout(()=>{document.querySelectorAll(".server-card").forEach(e=>{e.classList.remove("installing","uninstalling");const t=e.querySelector(".status-indicator");t&&t.classList.remove("installing","uninstalling")})},100)},100)}function E(e,t){if(!t||!t.name||""===t.name.trim())return null;const n=document.createElement("div");n.className="server-card";const o=f(e,t),a=t.requiredEnvVars&&t.requiredEnvVars.length>0;o&&n.setAttribute("data-selected","");const r=t.stars||0,s=r>0?`<div class="stars-info" title="GitHub Stars: ${r.toLocaleString()}"><svg class="star-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg> ${i=r,i>=1e3?(i/1e3).toFixed(1).replace(/\.0$/,"")+"k":i.toString()}</div>`:"";var i;const c=t.lastStarUpdate||t.updated_at;c&&r>0&&(new Date(c).toLocaleDateString(),function(e){const t=new Date,n=new Date(e),o=t-n,a=Math.floor(o/864e5);0===a||(1===a||(a<7||(a<30?Math.floor(a/7):a<365?Math.floor(a/30):Math.floor(a/365))))}(c));const l=(e=>{if(!e)return"";const t=e.match(/github\.com\/([^\/]+)/);return t?t[1]:""})(t.githubLink),d=l?`<div class="owner-name">by <span class="owner-username">${l}</span></div>`:"",u=t.logo&&""!==t.logo.trim()?`<div class="server-logo">\n <img src="${t.logo}" alt="${t.name} logo" loading="lazy" onerror="this.style.display='none';">\n </div>`:'<div class="server-logo-placeholder"></div>';return n.innerHTML=`\n <div>\n <div class="card-header">\n ${u}\n <div class="card-header-content">\n <div class="server-info">\n <h3>${t.githubLink?`<a href="${t.githubLink}" target="_blank" rel="noopener noreferrer">${t.name}</a>`:t.name}</h3>\n ${d}\n </div>\n <div class="stars-container">\n ${s}\n </div>\n </div>\n </div>\n <p class="description">${t.description} <span class="view-more-link" onclick="showReadme('${e}')" title="View README">View more</span></p>\n </div>\n <div class="card-footer">\n <div class="button-container">\n ${o?`<button class="btn-configure" onclick="configureServer('${e}')">Reconfigure</button>\n <button class="btn-uninstall" onclick="uninstallServer('${e}')">Remove</button>`:`<button class="btn-configure" onclick="configureServer('${e}')">Configure</button>\n ${a?"":`<button onclick="quickInstallServer('${e}')">Install</button>`}`}\n </div>\n <div class="status-indicator ${o?"installed":""}"></div>\n </div>\n `,a||(n.style.cursor="pointer",n.addEventListener("click",t=>{t.target.closest("button")||(o?window.uninstallServer(e,!0):window.quickInstallServer(e))})),n}function $(){const e=document.getElementById("currentServerList");e.innerHTML="",0!==Object.keys(n.mcpServers).length?Object.entries(n.mcpServers).forEach(([t,n])=>{const o=document.createElement("li");o.className="server-item",o.innerHTML=`\n <div class="server-info">\n <h4>${t}</h4>\n <div class="details">\n Command: ${n.command} ${n.args.join(" ")}\n ${n.env?"<br>Env: "+JSON.stringify(n.env):""}\n </div>\n </div>\n <div class="server-actions">\n <button class="btn-edit" onclick="editServer('${t}')">Edit</button>\n <button class="btn-remove" onclick="removeServer('${t}')">Remove</button>\n </div>\n `,e.appendChild(o)}):e.innerHTML='<li class="server-item">No servers configured</li>'}function k(e){}const I=new class{constructor(){this.baseURL="",this.cache=new Map,this.cacheTimeout=3e4}async getPaginatedServers(e={}){const t={page:1,limit:50,search:"",sortBy:"name",sortOrder:"asc",category:"",minStars:0,maxStars:999999,...e},n=JSON.stringify(t);if(this.cache.has(n)){const e=this.cache.get(n);if(Date.now()-e.timestamp<this.cacheTimeout)return e.data;this.cache.delete(n)}const o=`/api/servers/paginated?${new URLSearchParams(t).toString()}`;try{const e=await fetch(o);if(!e.ok)throw new Error(`HTTP error! status: ${e.status}`);const t=await e.json();return this.cache.set(n,{data:t,timestamp:Date.now()}),t}catch(a){throw a}}async getCategories(){const e="categories";if(this.cache.has(e)){const t=this.cache.get(e);if(Date.now()-t.timestamp<this.cacheTimeout)return t.data;this.cache.delete(e)}try{const t=await fetch("/api/servers/categories");if(!t.ok)throw new Error(`HTTP error! status: ${t.status}`);const n=await t.json();return this.cache.set(e,{data:n,timestamp:Date.now()}),n}catch(t){return[]}}async getSearchSuggestions(e,t=10){if(!e||e.length<2)return[];const n=`suggestions-${e}-${t}`;if(this.cache.has(n)){const e=this.cache.get(n);if(Date.now()-e.timestamp<this.cacheTimeout)return e.data;this.cache.delete(n)}const o=`/api/servers/search-suggestions?${new URLSearchParams({q:e,limit:t}).toString()}`;try{const e=await fetch(o);if(!e.ok)throw new Error(`HTTP error! status: ${e.status}`);const t=await e.json();return this.cache.set(n,{data:t,timestamp:Date.now()}),t}catch(a){return[]}}clearCache(){this.cache.clear()}cleanupCache(){const e=Date.now();for(const[t,n]of this.cache.entries())e-n.timestamp>this.cacheTimeout&&this.cache.delete(t)}};setInterval(()=>{I.cleanupCache()},6e4);let x=1,C=50,T=1,B=!1,L="";function M(e){i=e,x=1,N()}function O(e){c=e,x=1,N()}function P(e){m(parseInt(e)),x=1,N(),U(parseInt(e))}async function j(e){var t;null==(t=window.clearSelection)||t.call(window),L=e,x=1,await N()}async function N(){if(!B){B=!0,function(){const e=document.getElementById("serverGrid"),t=document.createElement("div");t.id="loadingState",t.innerHTML='\n <div style="text-align: center; padding: 40px;">\n <div class="loading-spinner"></div>\n <p>Loading servers...</p>\n </div>\n ',e.innerHTML="",e.appendChild(t)}();try{const e={page:x,limit:C,search:L,sortBy:c,sortOrder:"stars"===c?"desc":"asc",category:"",minStars:l,maxStars:999999},t=await I.getPaginatedServers(e);T=t.pagination.totalPages,function(e){const t=document.getElementById("serverGrid");t.innerHTML="","category"===i?function(e,t){const n={},o=Object.entries(e).map(([e,t])=>({key:e,server:t}));o.forEach(({key:e,server:t})=>{const o=t.category||"Other";n[o]||(n[o]=[]),n[o].push({key:e,server:t})});Object.keys(n).sort().forEach(e=>{const o=document.createElement("div");o.className="category-section collapsed",o.setAttribute("data-category",e),o.innerHTML=`\n <h2 class="category-title accordion-header" onclick="toggleCategory('${e}')">\n <span class="accordion-icon">▶</span>\n ${e} \n <span class="category-count">(${n[e].length})</span>\n </h2>\n <div class="category-grid accordion-content" data-category="${e}" style="display: none;">\n `,n[e].forEach(({key:e,server:t})=>{const n=q(e,t);o.querySelector(".category-grid").appendChild(n)}),o.innerHTML+="</div>",t.appendChild(o)})}(e,t):function(e,t){const n=document.createElement("div");n.className="category-grid",n.style.marginTop="0";const o=Object.entries(e).map(([e,t])=>({key:e,server:t}));o.forEach(({key:e,server:t})=>{const o=q(e,t);n.appendChild(o)}),t.appendChild(n)}(e,t);setTimeout(()=>{R(),"function"==typeof cleanupAnimations&&cleanupAnimations()},100)}(t.servers),H(t.pagination),0===Object.keys(t.servers).length&&function(){const e=document.getElementById("serverGrid"),t=L?` matching "${L}"`:"",n=l>0?` with ${l}+ stars`:"",o="";e.innerHTML=`\n <div style="text-align: center; padding: 40px; color: #666;">\n No servers found${t}${n}${o}.\n </div>\n `}()}catch(t){e=t.message,document.getElementById("serverGrid").innerHTML=`\n <div style="text-align: center; padding: 40px; color: #d73a49;">\n <p>Error loading servers: ${e}</p>\n <button onclick="loadAndDisplayServers()" class="btn-primary">Retry</button>\n </div>\n `}finally{B=!1,function(){const e=document.getElementById("loadingState");e&&e.remove()}()}var e}}function q(e,t){const n=document.createElement("div");n.className="server-card";const o=f(e,t),a=t.requiredEnvVars&&t.requiredEnvVars.length>0;o&&n.setAttribute("data-selected","");const r=t.stars||0,s=r>0?`<div class="stars-info" title="GitHub Stars: ${r.toLocaleString()}"><svg class="star-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg> ${i=r,i>=1e3?(i/1e3).toFixed(1).replace(/\.0$/,"")+"k":i.toString()}</div>`:"";var i;const c=t.lastStarUpdate||t.updated_at;c&&r>0&&(new Date(c).toLocaleDateString(),function(e){const t=new Date,n=new Date(e),o=t-n,a=Math.floor(o/864e5);0===a||(1===a||(a<7||(a<30?Math.floor(a/7):a<365?Math.floor(a/30):Math.floor(a/365))))}(c));const l=(e=>{if(!e)return"";const t=e.match(/github\.com\/([^\/]+)/);return t?t[1]:""})(t.githubLink),d=l?`<div class="owner-name">by <span class="owner-username">${l}</span></div>`:"",u=t.logo&&""!==t.logo.trim()?`<div class="server-logo">\n <img src="${t.logo}" alt="${t.name} logo" loading="lazy" onerror="this.style.display='none';">\n </div>`:'<div class="server-logo-placeholder"></div>';return n.innerHTML=`\n <div>\n <div class="card-header">\n ${u}\n <div class="card-header-content">\n <div class="server-info">\n <h3>${t.githubLink?`<a href="${t.githubLink}" target="_blank" rel="noopener noreferrer">${t.name}</a>`:t.name}</h3>\n ${d}\n </div>\n <div class="stars-container">\n ${s}\n </div>\n </div>\n </div>\n <p class="description">${t.description} <span class="view-more-link" onclick="showReadme('${e}')" title="View README">View more</span></p>\n </div>\n <div class="card-footer">\n <div class="button-container">\n ${o?`<button class="btn-configure" onclick="configureServer('${e}')">Reconfigure</button>\n <button class="btn-uninstall" onclick="uninstallServer('${e}')">Remove</button>`:`<button class="btn-configure" onclick="configureServer('${e}')">Configure</button>\n ${a?"":`<button onclick="quickInstallServer('${e}')">Install</button>`}`}\n </div>\n <div class="status-indicator ${o?"installed":""}"></div>\n </div>\n `,a||(n.style.cursor="pointer",n.addEventListener("click",t=>{t.target.closest("button")||(o?window.uninstallServer(e,!0):window.quickInstallServer(e))})),n}function H(e){if(!document.getElementById("paginationControls"))return function(){const e=document.getElementById("serverGrid"),t=document.createElement("div");t.id="paginationControls",t.className="pagination-controls",t.innerHTML='\n <div class="pagination-info">\n <span id="pageInfo">Loading...</span>\n <select id="limitSelect" onchange="handleLimitChange(this.value)">\n <option value="25">25 per page</option>\n <option value="50" selected>50 per page</option>\n <option value="100">100 per page</option>\n </select>\n </div>\n <div class="pagination-buttons">\n <button id="firstPageBtn" onclick="goToPage(1)" title="First page">⏮</button>\n <button id="prevPageBtn" onclick="goToPage(currentPage - 1)" title="Previous page">◀</button>\n <input type="number" id="pageInput" min="1" value="1" onchange="goToPage(parseInt(this.value))" title="Go to page">\n <button id="nextPageBtn" onclick="goToPage(currentPage + 1)" title="Next page">▶</button>\n <button id="lastPageBtn" onclick="goToPage(totalPages)" title="Last page">⏭</button>\n </div>\n ',e.parentNode.insertBefore(t,e.nextSibling)}(),H(e);const{page:t,totalPages:n,hasNext:o,hasPrev:a,total:r}=e;document.getElementById("pageInfo").textContent=`Page ${t} of ${n} (${r} servers)`,document.getElementById("prevPageBtn").disabled=!a,document.getElementById("nextPageBtn").disabled=!o,document.getElementById("firstPageBtn").disabled=!a,document.getElementById("lastPageBtn").disabled=!o,document.getElementById("pageInput").value=t,document.getElementById("pageInput").max=n}function A(e){e<1||e>T||e===x||(x=e,N())}function F(e){C=parseInt(e),x=1,N()}function U(e){const t=document.getElementById("activeFilterIndicator"),n=document.getElementById("activeFilterText");if(e>0){const o=e>=1e3?e/1e3+"k+ stars":`${e}+ stars`;n.textContent=o,t.style.display="flex"}else t.style.display="none"}function D(){m(0);const e=document.getElementById("starsFilter");e&&(e.value="0"),U(0),x=1,N()}function _(e){const t=JSON.parse(localStorage.getItem("expandedCategories")||"[]"),n=t.indexOf(e);-1===n?t.push(e):t.splice(n,1),localStorage.setItem("expandedCategories",JSON.stringify(t));const o=document.querySelector(`.category-section[data-category="${e}"]`);if(!o)return;const a=o.querySelector(".accordion-content"),r=o.querySelector(".accordion-icon");if(!a||!r)return;"none"===a.style.display?(a.style.display="grid",r.textContent="▼",o.classList.remove("collapsed")):(a.style.display="none",r.textContent="▶",o.classList.add("collapsed"))}function R(){JSON.parse(localStorage.getItem("expandedCategories")||"[]").forEach(e=>{const t=document.querySelector(`.category-section[data-category="${e}"]`);if(t){const e=t.querySelector(".accordion-content"),n=t.querySelector(".accordion-icon");e&&n&&(e.style.display="grid",n.textContent="▼",t.classList.remove("collapsed"))}})}function V(){window.searchServers=j,window.handleGroupByChange=M,window.handleSortByChange=O,window.handleStarsFilterChange=P,window.resetStarsFilter=D,window.toggleCategory=_,window.currentPage=x,window.totalPages=T,window.goToPage=A,window.handleLimitChange=F,window.restoreExpandedStates=R,function(){const e=document.getElementById("searchInput")||document.querySelector(".search-box");if(!e)return;let t=null;const n=e.cloneNode(!0);e.parentNode.replaceChild(n,e),n.addEventListener("input",e=>{clearTimeout(t),t=setTimeout(()=>{j(e.target.value)},300)}),n.addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),clearTimeout(t),j(e.target.value))})}(),N()}function J(e){const t=document.querySelector(`input[data-var-name="${e}"]`),n=document.querySelector(`.variable-item[data-var-name="${e}"]`),o=n.querySelector(".variable-save-btn");if(!t||!n||!o)return;const a=t.getAttribute("data-original-value")||"";t.value.trim()!==a?(o.classList.add("show"),n.classList.add("changed"),n.classList.remove("saved","saved-success")):(o.classList.remove("show"),n.classList.remove("changed","saved","saved-success"))}async function z(){const e={};document.querySelectorAll(".variable-input").forEach(t=>{const n=t.getAttribute("data-var-name"),o=t.value.trim();o&&(e[n]=o)});try{if(!(await fetch("/api/variables",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).ok)throw new Error("Failed to save variables");u(e),v("Variables saved successfully","success")}catch(t){v("Error saving variables: "+t.message,"error")}}function G(){p(-1);document.querySelectorAll(".server-card").forEach(e=>{e.classList.remove("keyboard-selected")})}function K(){const e=document.querySelector(".search-box");e.addEventListener("keydown",e=>{"Enter"===e.key?(e.preventDefault(),function(){if(W(),0===s.length)return;if(1===s.length){const e=X(s[0]);if(e){const t=o[e];if(f(e,t))window.configureServer(e);else{t.requiredEnvVars&&t.requiredEnvVars.length>0?window.configureServer(e):window.quickInstallServer(e)}}}else p(0),Z()}()):"ArrowDown"===e.key?(e.preventDefault(),Y(1)):"ArrowUp"===e.key&&(e.preventDefault(),Y(-1))}),document.addEventListener("keydown",t=>{"INPUT"!==t.target.tagName&&"TEXTAREA"!==t.target.tagName&&("ArrowDown"===t.key?(t.preventDefault(),Y(1)):"ArrowUp"===t.key?(t.preventDefault(),Y(-1)):"Enter"===t.key&&r>=0?(t.preventDefault(),function(){if(r>=0&&r<s.length){const e=X(s[r]);if(e){const t=o[e];if(f(e,t))window.configureServer(e);else{t.requiredEnvVars&&t.requiredEnvVars.length>0?window.configureServer(e):window.quickInstallServer(e)}}}}()):"Escape"===t.key&&(t.preventDefault(),G(),e.focus()))})}function W(){!function(e){s=e}(Array.from(document.querySelectorAll(".server-card:not(.uninstalling)")))}function Y(e){if(W(),0!==s.length){if(-1===r)p(e>0?0:s.length-1);else{let t=r+e;t<0?t=s.length-1:t>=s.length&&(t=0),p(t)}Z()}}function Z(){if(s.forEach(e=>{e.classList.remove("keyboard-selected")}),r>=0&&r<s.length){const e=s[r];e.classList.add("keyboard-selected"),e.scrollIntoView({behavior:"smooth",block:"nearest"})}}function X(e){const t=e.querySelector('button[onclick*="configureServer"]');if(t){const e=t.getAttribute("onclick").match(/configureServer\('([^']+)'\)/);if(e)return e[1]}return null}async function Q(){try{const e=await fetch("/api/version-check"),t=await e.json();t.needsUpdate&&t.isMandatory?function(e){const t=document.getElementById("serverModal"),n=document.getElementById("modalTitle"),o=document.getElementById("modalBody");n.textContent="🔄 Mandatory Update Required",o.innerHTML=`\n <div class="update-modal">\n <div style="text-align: center; margin-bottom: 20px;">\n <div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>\n <h3 style="color: #e74c3c; margin-bottom: 12px;">Update Required</h3>\n <p style="color: #666; margin-bottom: 20px;">\n A mandatory update is available. The application must be updated to continue.\n </p>\n </div>\n \n <div class="version-info" style="background: #f8f9fa; padding: 16px; border-radius: 8px; margin-bottom: 20px;">\n <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">\n <span style="font-weight: 600;">Current Version:</span>\n <span style="font-family: 'JetBrains Mono', monospace;">${e.currentVersion}</span>\n </div>\n <div style="display: flex; justify-content: space-between;">\n <span style="font-weight: 600;">Latest Version:</span>\n <span style="font-family: 'JetBrains Mono', monospace; color: #27ae60;">${e.latestVersion}</span>\n </div>\n </div>\n \n <div id="updateProgress" style="margin-bottom: 20px; display: none;">\n <div style="background: #e0e0e0; border-radius: 4px; overflow: hidden; margin-bottom: 8px;">\n <div id="progressBar" style="background: #3498db; height: 8px; width: 0%; transition: width 0.3s;"></div>\n </div>\n <div id="updateStatus" style="text-align: center; font-size: 14px; color: #666;">\n Preparing update...\n </div>\n </div>\n \n <div class="button-group">\n <button id="updateNowBtn" class="btn-primary" onclick="performAutoUpdate()" style="width: 100%;">\n 🔄 Update Now\n </button>\n </div>\n \n <div style="margin-top: 16px; padding: 12px; background: #fff3cd; border-radius: 6px; border-left: 4px solid #ffc107;">\n <small style="color: #856404;">\n <strong>Note:</strong> The application will restart automatically after the update completes.\n </small>\n </div>\n </div>\n `,t.style.display="block";const a=t.querySelector(".modal-close");a&&(a.style.display="none");t.onclick=null}(t):t.needsUpdate&&function(e){const t=document.getElementById("message");t.innerHTML=`\n <div style="display: flex; justify-content: space-between; align-items: center;">\n <span>🔄 Update available: v${e.latestVersion} (current: v${e.currentVersion})</span>\n <button onclick="performAutoUpdate()" class="btn-primary" style="padding: 4px 12px; font-size: 12px;">\n Update Now\n </button>\n </div>\n `,t.className="message success",t.style.display="block"}(t)}catch(e){}}async function ee(){await async function(){try{const e=await fetch("/api/config"),t=await e.json();d(t),w(),document.getElementById("configEditor").value=JSON.stringify(t,null,2),window.updateCurrentServers&&window.updateCurrentServers(),window.displayServers&&Object.keys(o).length>0&&window.displayServers()}catch(e){v("Failed to load configuration","error")}}(),await b(),await async function(){try{const e=await fetch("/api/servers"),t=await e.json(),n={};Object.entries(t).forEach(([e,t])=>{(void 0!==t.stars||t.lastStarUpdate)&&(n[e]={github:{stars:t.stars||0,fetched_at:t.lastStarUpdate||t.updated_at,updated_at:t.updated_at}})}),Object.keys(o).length}catch(e){}}(),await async function(){try{const e=await fetch("/api/variables");e.ok&&u(await e.json())}catch(e){}}(),await async function(){try{const e=await fetch("/api/project-info"),t=await e.json();document.getElementById("projectName").textContent=t.name,document.getElementById("projectType").textContent=t.type,"Local Project"!==t.type&&(document.title=`MCP Server Manager - ${t.name}`)}catch(e){document.getElementById("projectName").textContent="Unknown Project",document.getElementById("projectType").textContent=""}}(),V(),setTimeout(Q,2e3);const e=document.querySelector(".search-box");e&&e.focus(),K()}async function te(e){try{const t=await fetch("/api/env-variables"),n=await t.json();if(n.exists&&n.variables[e]){const t=document.getElementById(`env_${e}`),o=document.querySelector(`input[name="opt_${e}"]`);t?t.value=n.variables[e]:o&&(o.value=n.variables[e]),v(`Fetched ${e} from .env file`,"success")}else v(`${e} not found in .env file`,"error")}catch(t){v("Error fetching from .env file","error")}}window.onclick=function(e){const t=document.getElementById("serverModal");if(e.target===t&&y(),!e.target.closest(".fetch-dropdown")){document.querySelectorAll(".dropdown-content").forEach(e=>{e.style.display="none"})}},window.handleGroupByChange=function(e){},window.handleSortByChange=function(e){},window.handleStarsFilterChange=function(e){},window.resetStarsFilter=function(){m(0),document.getElementById("starsFilterSelect").value="0",function(){const e=document.getElementById("activeFilterIndicator");document.getElementById("activeFilterText"),e.style.display="none"}(),document.querySelector(".search-box").value},window.searchServers=k,window.updateCurrentServers=$,window.displayServers=S,window.toggleCategory=function(e){var t;JSON.parse(localStorage.getItem("expandedCategories")||"[]").includes(e)?function(e){try{const t=JSON.parse(localStorage.getItem("expandedCategories")||"[]").filter(t=>t!==e);localStorage.setItem("expandedCategories",JSON.stringify(t))}catch(t){}}(e):function(e){try{const t=JSON.parse(localStorage.getItem("expandedCategories")||"[]");t.includes(e)||(t.push(e),localStorage.setItem("expandedCategories",JSON.stringify(t)))}catch(t){}}(e);const n=document.querySelector(`.category-section[data-category="${e}"]`)||(null==(t=document.querySelector(`.accordion-content[data-category="${e}"]`))?void 0:t.parentElement);if(!n)return;const o=n.querySelector(".accordion-content"),a=n.querySelector(".accordion-icon");if(!o||!a)return;"none"===o.style.display?(o.style.display="grid",a.textContent="▼",n.classList.remove("collapsed")):(o.style.display="none",a.textContent="▶",n.classList.add("collapsed"))},window.configureServer=async function(e){const t=o[e],r=document.getElementById("serverModal"),s=document.getElementById("modalTitle"),i=document.getElementById("modalBody"),c=isServerInstalled(e,t),l=c?findExistingServerName(e,t):e,d=c?n.mcpServers[l]:null;s.textContent=`${c?"Reconfigure":"Configure"} ${t.name}`;let u={};try{const e=await fetch("/api/env-variables"),t=await e.json();t.exists&&(u=t.variables)}catch(g){}let p=null;try{const e=await fetch("/api/git-info"),t=await e.json();t.success&&t.gitInfo&&(p=t.gitInfo)}catch(g){}let m=`\n <form onsubmit="installServer(event, '${e}')">\n <div class="form-group">\n <label>Server Name</label>\n <input type="text" id="serverName" value="${l}" required>\n <small>Name for this server in your configuration</small>\n </div>\n `;t.requiredEnvVars&&t.requiredEnvVars.length>0&&(m+="<h4>Required Configuration</h4>",t.requiredEnvVars.forEach(e=>{const t="string"==typeof e?e:e.name,n="object"==typeof e?e.description:"",o="object"==typeof e?e.example:"";let r="";r=d&&d.env&&d.env[t]?d.env[t]:a[t]||"";const s=[];if(a[t]&&s.push({type:"global",label:"global variables",value:a[t]}),void 0!==u[t]&&s.push({type:"env",label:".env file",value:u[t]}),p&&("GITHUB_REPO"===t||"GITHUB_OWNER"===t)){const e="GITHUB_REPO"===t?p.fullName:p.owner;s.push({type:"git",label:"git repository",value:e})}const i=s.length>1,c=i||1===s.length?"90px":"8px";m+=`\n <div class="form-group">\n <label>${t}${n?": "+n:""}</label>\n <div style="position: relative;">\n <input type="text" name="env_${t}" id="env_${t}" value="${r}" required style="padding-right: ${c};" placeholder="${o||""}">\n ${s.length>0?i?`\n <div class="fetch-dropdown" style="position: absolute; right: 4px; top: 50%; transform: translateY(-50%);">\n <button type="button" class="btn-fetch-multi" onclick="toggleFetchDropdown('${t}')" title="Fetch from multiple sources">\n <span style="font-size: 12px;">📥 fetch ▼</span>\n </button>\n <div class="dropdown-content" id="dropdown-${t}" style="display: none;">\n ${s.map(e=>`\n <a href="#" onclick="fetchFromSource('${t}', '${e.type}'); return false;" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">\n 📋 ${e.label}: <span style="color: #666; font-size: 11px;">${e.value.length>20?e.value.substring(0,20)+"...":e.value}</span>\n </a>\n `).join("")}\n </div>\n </div>\n `:`\n <button type="button" class="btn-fetch-single" onclick="fetchFromSource('${t}', '${s[0].type}')" title="Fetch from ${s[0].label}">\n <span style="font-size: 12px;">📋 fetch from ${s[0].type}</span>\n </button>\n `:""}\n </div>\n ${r&&d&&d.env&&d.env[t]?'<small style="color: #3498db;">✓ Current server value</small>':r?'<small style="color: #27ae60;">✓ Using saved value from Variables tab</small>':""}\n </div>\n `})),t.optionalParams&&t.optionalParams.length>0&&(m+="<h4>Optional Parameters</h4>",t.optionalParams.forEach(e=>{const t="string"==typeof e?e:e.name,n="object"==typeof e?e.description:"",o="object"==typeof e?e.example:"";let r="";r=d&&d.env&&d.env[t]?d.env[t]:a[t]||"";const s=[];if(a[t]&&s.push({type:"global",label:"global variables",value:a[t]}),void 0!==u[t]&&s.push({type:"env",label:".env file",value:u[t]}),p&&("GITHUB_REPO"===t||"GITHUB_OWNER"===t)){const e="GITHUB_REPO"===t?p.fullName:p.owner;s.push({type:"git",label:"git repository",value:e})}const i=s.length>1,c=i||1===s.length?"90px":"8px";m+=`\n <div class="form-group">\n <label>${t}${n?": "+n:""}</label>\n <div style="position: relative;">\n <input type="text" name="opt_${t}" id="opt_${t}" value="${r}" style="padding-right: ${c};" placeholder="${o||""}">\n ${s.length>0?i?`\n <div class="fetch-dropdown" style="position: absolute; right: 4px; top: 50%; transform: translateY(-50%);">\n <button type="button" class="btn-fetch-multi" onclick="toggleFetchDropdown('opt_${t}')" title="Fetch from multiple sources">\n <span style="font-size: 12px;">📥 fetch ▼</span>\n </button>\n <div class="dropdown-content" id="dropdown-opt_${t}" style="display: none;">\n ${s.map(e=>`\n <a href="#" onclick="fetchFromSource('opt_${t}', '${e.type}'); return false;" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">\n 📋 ${e.label}: <span style="color: #666; font-size: 11px;">${e.value.length>20?e.value.substring(0,20)+"...":e.value}</span>\n </a>\n `).join("")}\n </div>\n </div>\n `:`\n <button type="button" class="btn-fetch-single" onclick="fetchFromSource('opt_${t}', '${s[0].type}')" title="Fetch from ${s[0].label}">\n <span style="font-size: 12px;">📋 fetch from ${s[0].type}</span>\n </button>\n `:""}\n </div>\n ${r&&d&&d.env&&d.env[e]?'<small style="color: #3498db;">✓ Current server value</small>':r?'<small style="color: #27ae60;">✓ Using saved value from Variables tab</small>':""}\n </div>\n `})),m+=`\n <div class="button-group">\n <button type="submit" class="btn-primary">${c?"Update Server":"Add Server"}</button>\n <button type="button" class="btn-secondary" onclick="closeModal()">Cancel</button>\n </div>\n </form>\n `,i.innerHTML=m,r.style.display="block"},window.installServer=async function(e,t){e.preventDefault();const r=o[t],s=e.target,i=s.serverName.value,c=isServerInstalled(t,r),l=c?findExistingServerName(t,r):null;try{let e;if("self"===r.installType&&r.githubLink){v(`Cloning repository for ${r.name}...`,"info");const n=await fetch("/api/clone-repository",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({serverKey:t})});if(!n.ok){const e=await n.json();throw new Error(e.error||"Failed to clone repository")}v(`Repository cloned successfully: ${(await n.json()).message}`,"success");const o=function(e){try{const t=[/^https?:\/\/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/,/^git@github\.com:([^\/]+)\/([^\/]+?)(?:\.git)?$/];for(const n of t){const t=e.match(n);if(t){const[,e,n]=t;return{owner:e,repo:n}}}return null}catch(t){return null}}(r.githubLink);if(!o)throw new Error("Invalid GitHub URL format");const{owner:a,repo:s}=o;e={command:"bash",args:["-c",`cd .mcp/${`${a}-${s}`} && ${r.installCommand}`],env:{}}}else{const t=r.installCommand.split(" "),n=t[0];e={command:n,args:[...t.slice(1)],env:{}}}const o=new FormData(s);for(const[t,n]of o.entries())if(t.startsWith("env_")&&n){const o=t.substring(4);e.env[o]=n,a[o]!==n&&(a[o]=n,z())}else if(t.startsWith("opt_")&&n){const o=t.substring(4);e.env[o]=n,a[o]!==n&&(a[o]=n,z())}c&&l&&l!==i&&delete n.mcpServers[l];const u={...n};u.mcpServers[i]=e,d(u);if(!(await fetch("/api/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)})).ok)throw new Error("Failed to save configuration");$(),document.getElementById("configEditor").value=JSON.stringify(u,null,2),y(),S(),v(`Successfully installed ${r.name}`,"success")}catch(u){v(`Failed to install ${r.name}: ${u.message}`,"error")}},window.quickInstallServer=async function(e){const t=o[e],a={...n};try{if("self"===t.installType&&t.githubLink){v(`Cloning repository for ${t.name}...`,"info");const o=await fetch("/api/clone-repository",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({serverKey:e})});if(!o.ok){const e=await o.json();throw new Error(e.error||"Failed to clone repository")}v(`Repository cloned successfully: ${(await o.json()).message}`,"success");const a=function(e){try{const t=[/^https?:\/\/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/,/^git@github\.com:([^\/]+)\/([^\/]+?)(?:\.git)?$/];for(const n of t){const t=e.match(n);if(t){const[,e,n]=t;return{owner:e,repo:n}}}return null}catch(t){return null}}(t.githubLink);if(!a)throw new Error("Invalid GitHub URL format");const{owner:r,repo:s}=a,i={command:"bash",args:["-c",`cd .mcp/${`${r}-${s}`} && ${t.installCommand}`]};t.optionalParams&&t.optionalParams.length>0&&(i.env={});const c={...n};c.mcpServers[e]=i,d(c),w(),window.updateCurrentServers&&window.updateCurrentServers(),document.getElementById("configEditor").value=JSON.stringify(c,null,2);if(!(await fetch("/api/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)})).ok)throw new Error("Failed to save configuration");v(`Successfully installed ${t.name}`,"success")}else{const o=t.installCommand.split(" "),a=o[0],r={command:a,args:[...o.slice(1)]};t.optionalParams&&t.optionalParams.length>0&&(r.env={});const s={...n};s.mcpServers[e]=r,d(s),w(),window.updateCurrentServers&&window.updateCurrentServers(),document.getElementById("configEditor").value=JSON.stringify(s,null,2);if(!(await fetch("/api/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).ok)throw new Error("Failed to save configuration");v(`Successfully installed ${t.name}`,"success")}}catch(r){d(a),w(),window.updateCurrentServers&&window.updateCurrentServers(),document.getElementById("configEditor").value=JSON.stringify(a,null,2),window.displayServers&&!window.USE_OPTIMIZED_UI&&window.displayServers(),v(`Failed to install ${t.name}: ${r.message}`,"error")}},window.uninstallServer=function(e,t=!1){const a=o[e];if(t||confirm(`Are you sure you want to uninstall ${a.name}?`)){const t={...n},o={...n};o.mcpServers[e]?delete o.mcpServers[e]:Object.entries(o.mcpServers).forEach(([e,t])=>{t.command+" "+t.args.join(" ")===a.installCommand&&delete o.mcpServers[e]}),d(o),w(),window.updateCurrentServers&&window.updateCurrentServers(),document.getElementById("configEditor").value=JSON.stringify(o,null,2),window.displayServers&&!window.USE_OPTIMIZED_UI&&window.displayServers(),fetch("/api/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}).then(e=>{if(!e.ok)throw new Error("Failed to save configuration")}).catch(e=>{d(t),w(),window.updateCurrentServers&&window.updateCurrentServers(),document.getElementById("configEditor").value=JSON.stringify(t,null,2),v(`Failed to uninstall ${a.name}: ${e.message}`,"error")})}},window.removeServer=function(e){if(confirm(`Are you sure you want to remove "${e}"?`)){const t={...n},o={...n};delete o.mcpServers[e],d(o),w(),window.updateCurrentServers&&window.updateCurrentServers(),document.getElementById("configEditor").value=JSON.stringify(o,null,2),window.displayServers&&!window.USE_OPTIMIZED_UI&&window.displayServers(),fetch("/api/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}).then(e=>{if(!e.ok)throw new Error("Failed to save configuration")}).catch(n=>{d(t),w(),window.updateCurrentServers&&window.updateCurrentServers(),document.getElementById("configEditor").value=JSON.stringify(t,null,2),v(`Failed to remove ${e}: ${n.message}`,"error")})}},window.saveConfig=async function(){try{const e=document.getElementById("configEditor").value,t=JSON.parse(e);if(!(await fetch("/api/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)throw new Error("Failed to save configuration");d(t),w(),v("Configuration saved successfully","success"),window.updateCurrentServers&&window.updateCurrentServers(),window.displayServers&&window.displayServers()}catch(e){v(e.message,"error")}},window.switchTab=function(e){var t;t=e,document.querySelectorAll(".tab").forEach(e=>e.classList.remove("active")),document.querySelectorAll(".tab-content").forEach(e=>e.classList.remove("active")),event.target.classList.add("active"),document.getElementById(t).classList.add("active"),"variables"===e&&async function(){const e=document.getElementById("variablesList");e.innerHTML="";const t={};Object.entries(o).forEach(([e,n])=>{n.requiredEnvVars&&n.requiredEnvVars.forEach(e=>{const o="string"==typeof e?e:e.name;t[o]||(t[o]=[]),t[o].push(n.name)})}),Object.entries(n.mcpServers).forEach(([e,n])=>{n.env&&Object.keys(n.env).forEach(n=>{t[n]||(t[n]=[]),t[n].includes(e)||t[n].push(e)})});let r={};try{const e=await fetch("/api/env-variables"),t=await e.json();t.exists&&(r=t.variables)}catch(s){}Object.entries(t).sort().forEach(([t,n])=>{const o=document.createElement("div");o.className="variable-item",o.setAttribute("data-var-name",t);const s=a[t]||"",i=void 0!==r[t],c=!s;o.innerHTML=`\n <div class="variable-header">\n <div class="variable-name">${t}</div>\n <button type="button" class="variable-save-btn" onclick="saveIndividualVariable('${t}')" title="Save this variable">\n Save\n </button>\n </div>\n <div style="position: relative;">\n <input type="text" \n class="variable-input" \n data-var-name="${t}"\n data-original-value="${s}"\n value="${s}"\n placeholder="Enter value for ${t}"\n oninput="handleVariableChange('${t}')"\n style="padding-right: ${i&&c?"110px":"8px"};">\n ${i&&c?`\n <button type="button" class="btn-env-fetch" onclick="fetchFromEnvForVariables('${t}')" title="Fetch from .env file">\n <span style="font-size: 12px;">📋 fetch from .env</span>\n </button>\n `:""}\n ${!c&&i?'\n <small style="color: #27ae60; font-size: 12px; display: block; margin-top: 4px;">✓ Value available in .env file</small>\n ':""}\n </div>\n <div class="variable-usage">\n <strong>Used by:</strong> ${n.join(", ")}\n </div>\n `,e.appendChild(o)}),0===Object.keys(t).length&&(e.innerHTML='<p style="text-align: center; color: #666; padding: 20px;">No environment variables found in configured servers.</p>')}()},window.closeModal=y,window.showMessage=v,window.clearSelection=G,window.handleVariableChange=J,window.saveIndividualVariable=async function(e){const t=document.querySelector(`input[data-var-name="${e}"]`),n=document.querySelector(`.variable-item[data-var-name="${e}"]`),o=n.querySelector(".variable-save-btn");if(!t||!n||!o)return;const r=t.value.trim();try{const s={...a};r?s[e]=r:delete s[e];if(!(await fetch("/api/variables",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).ok)throw new Error("Failed to save variable");u(s),t.setAttribute("data-original-value",r),o.classList.remove("show"),n.classList.remove("changed"),n.classList.add("saved-success"),v(`Variable ${e} saved successfully`,"success")}catch(s){v(`Error saving ${e}: `+s.message,"error")}},window.saveVariables=z,window.fetchFromEnvForVariables=async function(e){try{const t=await fetch("/api/env-variables"),n=await t.json();if(n.exists&&n.variables[e]){const t=document.querySelector(`input[data-var-name="${e}"]`);t&&(t.value=n.variables[e],J(e),v(`Fetched ${e} from .env file`,"success"))}else v(`${e} not found in .env file`,"error")}catch(t){v("Error fetching from .env file","error")}},window.performAutoUpdate=async function(){const e=document.getElementById("updateNowBtn"),t=document.getElementById("updateProgress"),n=document.getElementById("progressBar"),o=document.getElementById("updateStatus");e&&(e.disabled=!0,e.textContent="🔄 Updating..."),t&&(t.style.display="block");let a=0;const r=setInterval(()=>{a+=15*Math.random(),a>90&&(a=90),n&&(n.style.width=a+"%"),o&&(o.textContent=a<30?"Downloading update...":a<60?"Installing update...":"Finalizing installation...")},500);try{const t=await fetch("/api/auto-update",{method:"POST",headers:{"Content-Type":"application/json"}}),a=await t.json();clearInterval(r),n&&(n.style.width="100%"),a.success?(o&&(o.textContent="Update completed! Restarting...",o.style.color="#27ae60"),setTimeout(()=>{v("Update completed successfully! The page will reload automatically.","success"),setTimeout(()=>{window.location.reload()},2e3)},1e3)):(o&&(o.textContent="Update failed: "+a.message,o.style.color="#e74c3c"),e&&(e.disabled=!1,e.textContent="🔄 Retry Update"),v("Update failed: "+a.message,"error"))}catch(s){clearInterval(r),o&&(o.textContent="Update failed: Network error",o.style.color="#e74c3c"),e&&(e.disabled=!1,e.textContent="🔄 Retry Update"),v("Update failed: "+s.message,"error")}},window.confirmStopServer=async function(){const e=document.getElementById("serverModal"),t=document.getElementById("modalTitle"),n=document.getElementById("modalBody");t.textContent="⚠️ Stop MCP Manager Server",n.innerHTML='\n <div style="text-align: center; padding: 20px;">\n <div style="font-size: 48px; margin-bottom: 20px;">🛑</div>\n <h3 style="margin-bottom: 20px; color: #e74c3c;">Are you sure you want to stop the server?</h3>\n <p style="margin-bottom: 30px; color: #666;">\n This will shut down the MCP Manager web interface.<br>\n You\'ll need to restart it from the command line to access it again.\n </p>\n <div class="button-group" style="justify-content: center; gap: 15px;">\n <button type="button" class="btn-