UNPKG

@cloudbase/cloudbase-mcp

Version:

CloudBase MCP Server — operate Tencent CloudBase (database, auth, functions, storage, hosting) from AI coding tools via the Model Context Protocol. Part of CloudBase AI Toolkit.

1,677 lines (1,508 loc) 153 kB
exports.id = 602; exports.ids = [602]; exports.modules = { /***/ 6975: /***/ ((__unused_webpack_module, exports) => { "use strict"; /** * CSS styles for env setup page */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.CSS_STYLES = void 0; exports.CSS_STYLES = ` <style> @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap'); * { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary-color: #1a1a1a; --primary-hover: #000000; --accent-color: #67E9E9; --accent-hover: #2BCCCC; --text-primary: #ffffff; --text-secondary: #a0a0a0; --border-color: rgba(255, 255, 255, 0.15); --bg-secondary: rgba(255, 255, 255, 0.08); --bg-glass: rgba(26, 26, 26, 0.95); --shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2); --font-mono: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace; --header-bg: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0d1117 100%); } body { font-family: var(--font-mono); background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; position: relative; overflow-x: hidden; overflow-y: auto; } /* Custom scrollbar styles */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.05); border-radius: 4px; } ::-webkit-scrollbar-thumb { background: var(--accent-color); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: var(--accent-hover); } body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.02)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>') repeat; pointer-events: none; z-index: -1; } body::after { content: ''; position: fixed; top: 50%; left: 50%; width: 500px; height: 500px; background: radial-gradient(circle, rgba(103, 233, 233, 0.05) 0%, transparent 70%); transform: translate(-50%, -50%); pointer-events: none; z-index: -1; animation: pulse 8s ease-in-out infinite; } @keyframes pulse { 0%, 100% { opacity: 0.3; transform: translate(-50%, -50%) scale(1); } 50% { opacity: 0.6; transform: translate(-50%, -50%) scale(1.1); } } .modal { background: var(--bg-glass); backdrop-filter: blur(20px); border-radius: 20px; box-shadow: var(--shadow); border: 1px solid var(--border-color); width: 100%; max-width: 640px; overflow: hidden; animation: modalIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275); position: relative; } .modal::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.02) 50%, transparent 70%); animation: shimmer 3s infinite; pointer-events: none; } @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } @keyframes modalIn { from { opacity: 0; transform: scale(0.9) translateY(-20px); } to { opacity: 1; transform: scale(1) translateY(0); } } .header { background: var(--header-bg); color: var(--text-primary); padding: 20px 32px; display: flex; align-items: center; justify-content: space-between; position: relative; overflow: hidden; } .header::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.03) 50%, transparent 70%); animation: headerShimmer 4s infinite; pointer-events: none; } @keyframes headerShimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } .header-left { display: flex; align-items: center; gap: 16px; z-index: 1; } .logo { width: 32px; height: 32px; filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2)); animation: logoFloat 3s ease-in-out infinite; } @keyframes logoFloat { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-3px); } } .title { font-size: 18px; font-weight: 600; text-shadow: 0 2px 4px rgba(0,0,0,0.1); } .header-right { display: flex; align-items: center; gap: 16px; z-index: 1; } .account-section { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border-color); border-radius: 8px; } .account-uin { font-size: 12px; color: var(--text-secondary); font-family: var(--font-mono); } .btn-switch { display: flex; align-items: center; justify-content: center; padding: 4px; background: transparent; border: none; color: var(--text-secondary); cursor: pointer; transition: all 0.3s ease; border-radius: 4px; } .btn-switch:hover { background: rgba(255, 255, 255, 0.1); color: var(--accent-color); } .btn-icon-link { display: flex; align-items: center; justify-content: center; padding: 7px; background: transparent; border: 1px solid var(--border-color); border-radius: 8px; color: var(--text-primary); cursor: pointer; transition: all 0.3s ease; text-decoration: none; } .btn-icon-link:hover { background: rgba(255, 255, 255, 0.08); border-color: var(--accent-color); } .content { padding: 32px 32px 28px; } .content-title { font-size: 20px; font-weight: 600; color: var(--text-primary); margin-bottom: 28px; text-align: center; letter-spacing: -0.01em; } /* Search section styles */ .search-section { display: flex; gap: 10px; margin-bottom: 24px; } .search-box { position: relative; flex: 1; } .search-icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--text-secondary); pointer-events: none; z-index: 1; } .search-input { width: 100%; padding: 11px 38px 11px 38px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 10px; color: var(--text-primary); font-size: 13px; font-family: var(--font-mono); transition: all 0.3s ease; } .search-input:focus { outline: none; border-color: var(--accent-color); background: rgba(255, 255, 255, 0.1); box-shadow: 0 0 0 3px rgba(103, 233, 233, 0.08); } .search-input::placeholder { color: var(--text-secondary); opacity: 0.6; } .search-clear { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: all 0.3s ease; z-index: 1; } .search-clear:hover { color: var(--text-primary); background: rgba(255, 255, 255, 0.1); } .search-actions { display: flex; gap: 8px; } .btn-action { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 11px 14px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 10px; color: var(--text-secondary); cursor: pointer; transition: all 0.3s ease; } .btn-action:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(103, 233, 233, 0.5); color: var(--accent-color); } .btn-action:active { transform: scale(0.95); } .btn-text { font-size: 13px; font-weight: 500; } .env-list { max-height: 340px; overflow-y: auto; padding-right: 6px; margin-bottom: 24px; } .env-list::-webkit-scrollbar { width: 5px; } .env-list::-webkit-scrollbar-track { background: transparent; } .env-list::-webkit-scrollbar-thumb { background: var(--accent-color); border-radius: 3px; } /* Env item styles */ .env-item { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 10px; margin-bottom: 10px; cursor: pointer; transition: all 0.3s ease; position: relative; } .env-item:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(103, 233, 233, 0.5); transform: translateX(2px); } .env-item.selected { background: rgba(103, 233, 233, 0.12); border-color: var(--accent-color); box-shadow: 0 0 0 1px rgba(103, 233, 233, 0.2); } .env-item.selected::after { content: ''; position: absolute; right: 16px; top: 50%; transform: translateY(-50%); width: 6px; height: 6px; background: var(--accent-color); border-radius: 50%; box-shadow: 0 0 8px var(--accent-color); } .env-info { flex: 1; min-width: 0; } .env-name { font-size: 15px; font-weight: 500; color: var(--text-primary); margin-bottom: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; letter-spacing: -0.01em; } .env-name.unnamed { opacity: 0.5; } .env-id { font-size: 11px; color: var(--text-secondary); font-family: var(--font-mono); word-break: break-all; opacity: 0.8; } /* Empty state */ .empty-state { padding: 60px 20px; text-align: center; } .empty-message { font-size: 14px; color: var(--text-secondary); opacity: 0.7; line-height: 1.6; } .create-env-link { color: var(--accent-color); text-decoration: none; border-bottom: 1px solid transparent; transition: all 0.3s ease; } .create-env-link:hover { border-bottom-color: var(--accent-color); opacity: 0.8; } /* Action buttons */ .actions { display: flex; gap: 10px; margin-top: 28px; } .btn { flex: 1; padding: 12px 20px; border: none; border-radius: 10px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 6px; font-family: var(--font-mono); letter-spacing: -0.01em; } .btn-primary { background: var(--accent-color); color: var(--primary-color); border: 1px solid var(--accent-color); } .btn-primary:hover:not(:disabled) { background: var(--accent-hover); box-shadow: 0 4px 12px rgba(103, 233, 233, 0.3); } .btn-primary:disabled { background: var(--bg-secondary); color: var(--text-secondary); cursor: not-allowed; opacity: 0.5; border-color: var(--border-color); } .btn-secondary { background: transparent; color: var(--text-primary); border: 1px solid var(--border-color); } .btn-secondary:hover { background: var(--bg-secondary); border-color: rgba(255, 255, 255, 0.3); } /* Help links */ .help-links { display: flex; gap: 20px; justify-content: center; margin-top: 24px; padding-top: 24px; border-top: 1px solid rgba(255, 255, 255, 0.06); } .help-link { display: flex; align-items: center; gap: 6px; color: var(--text-secondary); text-decoration: none; font-size: 12px; padding: 6px 10px; border-radius: 6px; transition: all 0.3s ease; opacity: 0.8; } .help-link:hover { background: rgba(103, 233, 233, 0.08); color: var(--accent-color); opacity: 1; } /* Loading state */ .loading { display: none; flex-direction: column; align-items: center; justify-content: center; padding: 40px; gap: 20px; } .spinner { width: 40px; height: 40px; border: 3px solid var(--border-color); border-top-color: var(--accent-color); border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .loading span { color: var(--text-secondary); font-size: 14px; } /* Success state */ .success-state { text-align: center; padding: 40px 20px; animation: fadeInUp 0.8s ease-out both; } .success-icon { margin-bottom: 20px; color: var(--accent-color); animation: successPulse 2s ease-in-out infinite; } @keyframes successPulse { 0%, 100% { transform: scale(1); filter: drop-shadow(0 0 8px rgba(103, 233, 233, 0.3)); } 50% { transform: scale(1.1); filter: drop-shadow(0 0 16px rgba(103, 233, 233, 0.6)); } } .success-title { font-size: 24px; font-weight: 700; color: var(--text-primary); margin-bottom: 12px; } .success-message { color: var(--text-secondary); font-size: 16px; line-height: 1.5; } .selected-env-info { margin-top: 20px; padding: 16px; background: rgba(103, 233, 233, 0.1); border: 1px solid var(--accent-color); border-radius: 12px; display: flex; align-items: center; gap: 12px; } .env-label { color: var(--text-secondary); font-size: 14px; font-weight: 500; } .env-value { color: var(--accent-color); font-size: 16px; font-weight: 600; font-family: var(--font-mono); } /* Info banner (for non-critical notifications) */ .info-banner { margin-bottom: 20px; padding: 16px; background: rgba(103, 233, 233, 0.08); border: 1px solid rgba(103, 233, 233, 0.2); border-radius: 12px; } .info-item { margin-bottom: 16px; } .info-item:last-child { margin-bottom: 0; } .info-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } .info-header svg { color: var(--accent-color); } .info-title { color: var(--text-primary); font-weight: 600; font-size: 14px; } .info-message { color: var(--text-secondary); font-size: 13px; line-height: 1.5; margin-bottom: 12px; } .info-details { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; font-size: 12px; color: var(--text-secondary); opacity: 0.7; } .detail-label { font-weight: 500; } .detail-value { font-family: var(--font-mono); opacity: 0.8; } .error-action { display: flex; gap: 8px; flex-wrap: wrap; } .error-link { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; background: rgba(103, 233, 233, 0.15); border: 1px solid rgba(103, 233, 233, 0.3); border-radius: 6px; color: var(--accent-color); text-decoration: none; font-size: 13px; cursor: pointer; transition: all 0.3s ease; } .error-link:hover { background: rgba(103, 233, 233, 0.25); border-color: var(--accent-color); } .error-retry-btn { background: rgba(103, 233, 233, 0.2); border-color: var(--accent-color); color: var(--accent-color); } .error-retry-btn:hover { background: rgba(103, 233, 233, 0.3); } </style> `; /***/ }), /***/ 19246: /***/ ((module) => { function webpackEmptyContext(req) { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } webpackEmptyContext.keys = () => ([]); webpackEmptyContext.resolve = webpackEmptyContext; webpackEmptyContext.id = 19246; module.exports = webpackEmptyContext; /***/ }), /***/ 47053: /***/ ((__unused_webpack_module, exports) => { "use strict"; /** * JavaScript logic for env setup page */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.getJavaScripts = getJavaScripts; function getJavaScripts(wsPort) { return ` <script> let selectedEnvId = null; // Select environment function selectEnv(envId, element) { console.log('[env-setup] Selecting environment:', envId); selectedEnvId = envId; // Remove selected class from all items document.querySelectorAll('.env-item').forEach(item => { item.classList.remove('selected'); }); // Add selected class to current item element.classList.add('selected'); // Enable confirm button document.getElementById('confirmBtn').disabled = false; } // Filter environments function filterEnvs(searchTerm) { const items = document.querySelectorAll('.env-item'); const searchClear = document.getElementById('searchClear'); let visibleCount = 0; items.forEach(item => { const name = item.querySelector('.env-name').textContent.toLowerCase(); const id = item.querySelector('.env-id').textContent.toLowerCase(); const match = name.includes(searchTerm.toLowerCase()) || id.includes(searchTerm.toLowerCase()); item.style.display = match ? 'flex' : 'none'; if (match) visibleCount++; }); // Show/hide clear button if (searchClear) { searchClear.style.display = searchTerm ? 'flex' : 'none'; } // Show/hide no results message const noResults = document.getElementById('noResults'); if (noResults) { const hasItems = items.length > 0; noResults.style.display = visibleCount === 0 && hasItems ? 'block' : 'none'; } } // Clear search function clearSearch() { const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = ''; filterEnvs(''); searchInput.focus(); } } // WebSocket: same host as page so it works in remote VS Code / Cloud IDE (no localhost) const wsScheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = wsScheme + '//' + window.location.host; const ws = new WebSocket(wsUrl); ws.onopen = () => { console.log('[env-setup] WebSocket connected'); // Send session ID to server to associate this WebSocket with the session const sessionId = window.location.pathname.split('/').pop(); ws.send(JSON.stringify({ type: 'registerSession', sessionId: sessionId })); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); console.log('[env-setup] Received message:', data); if (data.type === 'success' && data.envId) { showSuccess(data.envId); } else if (data.type === 'envListRefreshed') { if (data.success) { console.log('[env-setup] Environment list refreshed, reloading page'); // Reload the page to show updated environment list window.location.reload(); } else { console.error('[env-setup] Failed to refresh environment list:', data.error); alert('刷新环境列表失败: ' + (data.error || '未知错误')); } } }; ws.onerror = (error) => { console.error('[env-setup] WebSocket error:', error); }; ws.onclose = () => { console.log('[env-setup] WebSocket closed'); }; // Show success state function showSuccess(envId) { document.querySelector('.content').style.display = 'none'; const successState = document.getElementById('successState'); const selectedEnvDisplay = document.getElementById('selectedEnvDisplay'); if (successState && selectedEnvDisplay) { selectedEnvDisplay.textContent = envId; successState.style.display = 'block'; } } // Confirm selection function confirm() { if (!selectedEnvId) { console.warn('[env-setup] No environment selected'); return; } console.log('[env-setup] Confirming selection:', selectedEnvId); if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'envId', data: selectedEnvId, cancelled: false })); // Close window immediately after sending confirmation setTimeout(() => { window.close(); }, 100); } else { console.error('[env-setup] WebSocket not connected'); alert('连接已断开,请刷新页面重试'); } } // Cancel function cancel() { console.log('[env-setup] Cancelling'); if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'envId', data: null, cancelled: true })); // Close window immediately after sending cancellation setTimeout(() => { window.close(); }, 100); } } // Switch account function switchAccount() { console.log('[env-setup] Switching account'); if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'envId', data: null, switch: true })); // Close window immediately after sending switch request setTimeout(() => { window.close(); }, 100); } } // Universal URL opener - works with all IDEs including CodeBuddy async function openUrl(event, url) { if (event) { event.preventDefault(); } console.log('[env-setup] Opening URL:', url); try { const response = await fetch('/api/open-url', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url }), }); const result = await response.json(); if (!result.success) { console.error('[env-setup] Failed to open URL:', result.error); // Fallback to direct open if API fails window.open(url, '_blank'); } } catch (error) { console.error('[env-setup] Error opening URL:', error); // Fallback to direct open if API fails window.open(url, '_blank'); } } // Create new environment function createNewEnv() { console.log('[env-setup] Creating new environment'); openUrl(null, 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'); } // Refresh environment list function refreshEnvList() { console.log('[env-setup] Refreshing environment list'); if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'refreshEnvList' })); // Add rotation animation const btn = event.target.closest('.btn-refresh'); if (btn) { btn.style.transform = 'rotate(360deg)'; setTimeout(() => { btn.style.transform = 'rotate(0deg)'; }, 600); } } } // Retry init TCB function retryInitTcb(sessionId) { console.log('[env-setup] Retrying TCB initialization:', sessionId); if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'retryInitTcb', sessionId: sessionId })); } } // Initialize on page load window.addEventListener('load', () => { // Auto-select first environment if exists const firstEnvItem = document.querySelector('.env-item'); if (firstEnvItem && !selectedEnvId) { const envId = firstEnvItem.getAttribute('onclick')?.match(/selectEnv\\('([^']+)'/)?.[1]; if (envId) { selectEnv(envId, firstEnvItem); console.log('[env-setup] Auto-selected first environment:', envId); } } }); </script> `.trim(); } /***/ }), /***/ 64112: /***/ ((__unused_webpack_module, exports) => { "use strict"; /** * HTML template structure */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.buildHTMLPage = buildHTMLPage; function buildHTMLPage(css, body, scripts) { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CloudBase MCP - 环境配置</title> ${css} </head> <body> ${body} ${scripts} </body> </html> `.trim(); } /***/ }), /***/ 67625: /***/ ((__unused_webpack_module, exports) => { "use strict"; /** * Reusable UI components for env setup page */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.renderHeader = renderHeader; exports.renderSearchBox = renderSearchBox; exports.renderEnvItem = renderEnvItem; exports.renderEmptyState = renderEmptyState; exports.renderNoResultsState = renderNoResultsState; exports.renderHelpLinks = renderHelpLinks; exports.renderActionButtons = renderActionButtons; exports.renderLoadingState = renderLoadingState; exports.renderSuccessState = renderSuccessState; exports.renderErrorBanner = renderErrorBanner; function renderHeader(accountInfo, ide) { const hasAccount = !!accountInfo?.uin; const isCodeBuddy = ide === "CodeBuddy"; return ` <div class="header"> <div class="header-left"> <img class="logo" src="https://7463-tcb-advanced-a656fc-1257967285.tcb.qcloud.la/mcp/cloudbase-logo.svg" alt="CloudBase Logo" /> <span class="title">CloudBase</span> </div> ${(hasAccount && accountInfo.region !== 'ap-singapore') ? /** 国际站不支持切 */ ` <div class="header-right"> <div class="account-section"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> <circle cx="12" cy="7" r="4"/> </svg> <span class="account-uin">${accountInfo.uin}</span> ${accountInfo.region ? `<span class="account-region">${accountInfo.region}</span>` : ''} ${!isCodeBuddy ? ` <button class="btn-switch" onclick="switchAccount()" title="切换账号"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <path d="M17 2l4 4-4 4M7 22l-4-4 4-4M21 6H10M3 18h11"/> </svg> </button> ` : ''} </div> </div> ` : ''} </div> `.trim(); } function renderSearchBox() { return ` <div class="search-section"> <div class="search-box"> <svg class="search-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <circle cx="11" cy="11" r="8"/> <path d="m21 21-4.35-4.35"/> </svg> <input type="text" id="searchInput" class="search-input" placeholder="搜索环境名称或 ID..." oninput="filterEnvs(this.value)" /> <button class="search-clear" onclick="clearSearch()" id="searchClear" style="display: none;"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <path d="M18 6L6 18M6 6l12 12"/> </svg> </button> </div> <div class="search-actions"> <button class="btn-action" onclick="createNewEnv()" title="新建环境"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <path d="M12 5v14M5 12h14"/> </svg> <span class="btn-text">新建环境</span> </button> <button class="btn-action" onclick="refreshEnvList()" title="刷新环境列表"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/> </svg> </button> </div> </div> `.trim(); } function renderEnvItem(env, index) { const alias = env.Alias || '(未命名)'; const envId = env.EnvId; const hasAlias = !!env.Alias; return ` <div class="env-item" onclick="selectEnv('${envId}', this)"> <div class="env-info"> <div class="env-name ${!hasAlias ? 'unnamed' : ''}">${alias}</div> <div class="env-id">${envId}</div> </div> </div> `.trim(); } function renderEmptyState(hasInitError) { return ` <div class="empty-state"> <p class="empty-message"> 暂无环境,请先<a href="https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp" onclick="openUrl(event, 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'); return false;" class="create-env-link">创建环境</a>,然后点击刷新按钮重试 </p> </div> `.trim(); } function renderNoResultsState() { return ` <div class="empty-state" id="noResults" style="display: none;"> <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="opacity: 0.3;"> <circle cx="11" cy="11" r="8"/> <path d="m21 21-4.35-4.35"/> </svg> <p class="empty-message">未找到匹配的环境</p> </div> `.trim(); } function renderHelpLinks() { return ` <div class="help-links"> <a href="https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/" onclick="openUrl(event, 'https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/'); return false;" class="help-link"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> <polyline points="14 2 14 8 20 8"/> <line x1="16" y1="13" x2="8" y2="13"/> <line x1="16" y1="17" x2="8" y2="17"/> </svg> 帮助文档 </a> <a href="https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/tutorials" onclick="openUrl(event, 'https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/tutorials'); return false;" class="help-link"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <polygon points="23 7 16 12 23 17 23 7"/> <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/> </svg> 视频教程 </a> <a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" onclick="openUrl(event, 'https://github.com/TencentCloudBase/CloudBase-AI-ToolKit'); return false;" class="help-link"> <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"> <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/> </svg> GitHub </a> </div> `.trim(); } function renderActionButtons(hasEnvs, hasInitError) { return ` <div class="actions"> <button class="btn btn-secondary" onclick="cancel()"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <path d="M18 6L6 18M6 6l12 12"/> </svg> 取消 </button> <button class="btn btn-primary" id="confirmBtn" onclick="confirm()" disabled> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <path d="M20 6L9 17l-5-5"/> </svg> 确认选择 </button> </div> `.trim(); } function renderLoadingState() { return ` <div class="loading" id="loading" style="display: none;"> <div class="spinner"></div> <span>正在配置环境...</span> </div> `.trim(); } function renderSuccessState() { return ` <div class="success-state" id="successState" style="display: none;"> <div class="success-icon"> <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M20 6L9 17l-5-5"/> </svg> </div> <h2 class="success-title">环境配置成功!</h2> <p class="success-message">已成功选择 CloudBase 环境</p> <div class="selected-env-info"> <span class="env-label">环境 ID:</span> <span class="env-value" id="selectedEnvDisplay"></span> </div> </div> `.trim(); } function renderErrorBanner(errorContext, sessionId) { const initTcbError = errorContext?.initTcbError; const createEnvError = errorContext?.createEnvError; // Only show initTcbError, hide createEnvError if (!initTcbError) { return ''; } return ` <div class="info-banner" id="errorBanner"> ${initTcbError ? ` <div class="info-item"> <div class="info-header"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="10"/> <line x1="12" y1="8" x2="12" y2="12"/> <line x1="12" y1="16" x2="12.01" y2="16"/> </svg> <span class="info-title">${escapeHtml(initTcbError.message)}</span> </div> <div class="info-message">${escapeHtml(initTcbError.actionText || '')}</div> ${initTcbError.requestId ? ` <div class="info-details"> <span class="detail-label">错误码:</span> <span class="detail-value">${escapeHtml(initTcbError.code)}</span> <span class="detail-label">请求 ID:</span> <span class="detail-value">${escapeHtml(initTcbError.requestId)}</span> </div> ` : ''} ${initTcbError.needRealNameAuth ? ` <div class="error-action"> <a href="${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}" onclick="openUrl(event, '${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}'); return false;" class="error-link"> 前往实名认证 </a> </div> ` : ''} ${initTcbError.needCamAuth ? ` <div class="error-action"> <a href="${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}" onclick="openUrl(event, '${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}'); return false;" class="error-link"> 前往开通 CloudBase 服务 </a> ${sessionId ? ` <button class="error-link error-retry-btn" onclick="retryInitTcb('${sessionId}')"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/> </svg> 重试 </button> ` : ''} </div> ` : ''} ${!initTcbError.needRealNameAuth && !initTcbError.needCamAuth && initTcbError.helpUrl ? ` <div class="error-action"> <a href="${initTcbError.helpUrl}" onclick="openUrl(event, '${initTcbError.helpUrl}'); return false;" class="error-link"> 前往开通 CloudBase 服务 </a> ${sessionId ? ` <button class="error-link error-retry-btn" onclick="retryInitTcb('${sessionId}')"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/> </svg> 重试 </button> ` : ''} </div> ` : ''} </div> ` : ''} </div> `.trim(); } // Helper function to escape HTML function escapeHtml(text) { const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }; return text.replace(/[&<>"']/g, (m) => map[m]); } /***/ }), /***/ 79529: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; /** * Main renderer for env setup page */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.renderEnvSetupPage = renderEnvSetupPage; const components_js_1 = __webpack_require__(67625); const html_js_1 = __webpack_require__(64112); const scripts_js_1 = __webpack_require__(47053); const styles_js_1 = __webpack_require__(6975); /** * Render the complete env setup page */ function renderEnvSetupPage(options) { const { envs = [], accountInfo, errorContext, sessionId, wsPort, ide } = options; const hasEnvs = envs.length > 0; const hasInitError = !!errorContext?.initTcbError; const hasErrors = !!(errorContext?.initTcbError || errorContext?.createEnvError); // Build env list HTML let envListHTML = ''; if (hasEnvs) { envListHTML = ` ${(0, components_js_1.renderSearchBox)()} <div class="env-list" id="envList"> ${envs.map((env, index) => (0, components_js_1.renderEnvItem)(env, index)).join('\n')} </div> ${(0, components_js_1.renderNoResultsState)()} `; } else { // 暂无环境时也显示搜索框、新建和刷新按钮 envListHTML = ` ${(0, components_js_1.renderSearchBox)()} <div class="env-list" id="envList"> ${(0, components_js_1.renderEmptyState)(hasInitError)} </div> ${(0, components_js_1.renderNoResultsState)()} `; } // Build body HTML const bodyHTML = ` <div class="modal"> ${(0, components_js_1.renderHeader)(accountInfo, ide)} <div class="content"> <h1 class="content-title">选择环境</h1> ${hasErrors ? (0, components_js_1.renderErrorBanner)(errorContext, sessionId) : ''} ${envListHTML} ${(0, components_js_1.renderActionButtons)(hasEnvs, hasInitError)} ${(0, components_js_1.renderHelpLinks)()} ${(0, components_js_1.renderLoadingState)()} ${(0, components_js_1.renderSuccessState)()} </div> </div> `; // Get JavaScript with WebSocket port const scripts = (0, scripts_js_1.getJavaScripts)(wsPort); // Assemble final HTML return (0, html_js_1.buildHTMLPage)(styles_js_1.CSS_STYLES, bodyHTML, scripts); } /***/ }), /***/ 92341: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.InteractiveServer = void 0; exports.getInteractiveServer = getInteractiveServer; exports.resetInteractiveServer = resetInteractiveServer; exports.getInteractiveServerSafe = getInteractiveServerSafe; const express_1 = __importDefault(__webpack_require__(26083)); const http_1 = __importDefault(__webpack_require__(81630)); const child_process_1 = __webpack_require__(79646); const open_1 = __importDefault(__webpack_require__(83440)); const ws_1 = __webpack_require__(17699); const index_js_1 = __webpack_require__(79529); const logger_js_1 = __webpack_require__(13039); function isVSCodeEnvironment() { return Boolean(process.env.VSCODE_IPC_HOOK_CLI || process.env.VSCODE_PID || process.env.TERM_PROGRAM === "vscode"); } /** * Validates that a URL is safe to open, blocking SSRF targets. * Only allows http/https schemes and rejects private/reserved IP ranges * and sensitive hostnames. */ function isUrlSafeToOpen(url) { let parsed; try { parsed = new URL(url); } catch { return { safe: false, reason: "Invalid URL format" }; } // Only allow http and https schemes if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { return { safe: false, reason: `Unsupported scheme: ${parsed.protocol}` }; } const hostname = parsed.hostname.toLowerCase(); // Block common sensitive / internal hostnames const blockedHostnames = [ "localhost", "localhost.localdomain", "ip6-localhost", "ip6-loopback", "metadata.google.internal", "metadata.internal", ]; if (blockedHostnames.includes(hostname)) { return { safe: false, reason: `Blocked hostname: ${hostname}` }; } // Block hostnames ending with .internal or .local if (hostname.endsWith(".internal") || hostname.endsWith(".local")) { return { safe: false, reason: `Blocked internal hostname: ${hostname}` }; } // Block IP addresses in private/reserved ranges (RFC 1918, loopback, link-local, etc.) const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; const match = ipv4Regex.exec(hostname); if (match) { const octets = [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10), parseInt(match[4], 10)]; // Loopback: 127.0.0.0/8 if (octets[0] === 127) { return { safe: false, reason: "Blocked loopback IP address" }; } // Link-local: 169.254.0.0/16 if (octets[0] === 169 && octets[1] === 254) { return { safe: false, reason: "Blocked link-local IP address" }; } // RFC 1918 private: 10.0.0.0/8 if (octets[0] === 10) { return { safe: false, reason: "Blocked private IP address (10.x.x.x)" }; } // RFC 1918 private: 172.16.0.0/12 if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) { return { safe: false, reason: "Blocked private IP address (172.16-31.x.x)" }; } // RFC 1918 private: 192.168.0.0/16 if (octets[0] === 192 && octets[1] === 168) { return { safe: false, reason: "Blocked private IP address (192.168.x.x)" }; } // Broadcast: 0.0.0.0 if (octets[0] === 0 && octets[1] === 0 && octets[2] === 0 && octets[3] === 0) { return { safe: false, reason: "Blocked broadcast address" }; } } // Block IPv6 loopback and link-local if (hostname.startsWith("[") && hostname.endsWith("]")) { const ipv6 = hostname.slice(1, -1).toLowerCase(); if (ipv6 === "::1" || ipv6 === "0:0:0:0:0:0:0:1" || ipv6 === "::" || ipv6 === "0:0:0:0:0:0:0:0") { return { safe: false, reason: "Blocked IPv6 loopback/reserved address" }; } if (ipv6.startsWith("fe80:")) { return { safe: false, reason: "Blocked IPv6 link-local address" }; } if (ipv6.startsWith("fc") || ipv6.startsWith("fd")) { return { safe: false, reason: "Blocked IPv6 unique-local address" }; } } return { safe: true }; } function parseBrowserCommand(browser) { const parts = browser.match(/(?:[^\s"]+|"[^"]*")+/g) || []; return parts.map((item) => item.replace(/^"(.*)"$/, "$1")); } async function openUrlByBrowserEnv(url) { const browser = process.env.BROWSER?.trim(); if (!browser) { return false; } const [command, ...args] = parseBrowserCommand(browser); if (!command) { return false; } const finalArgs = args.map((arg) => (arg === "%s" ? url : arg)); if (!args.includes("%s")) { finalArgs.push(url); } return new Promise((resolve) => { (0, child_process_1.execFile)(command, finalArgs, (execError) => { resolve(!execError); }); }); } function shouldUseBrowserEnvFallback() { return process.platform === "linux" && isVSCodeEnvironment(); } // Dynamic open behavior with IDE awareness and VSCode/Linux fallback async function openUrl(url, options, mcpServer) { // mcpServer 是 ExtendedMcpServer 实例,它有 server 和 ide 属性 // server 属性是 MCP server 的内部 server 实例,有 sendLoggingMessage 方法 const currentIde = mcpServer?.ide || process.env.INTEGRATION_IDE; const internalServer = mcpServer?.server; // 内部的 server 实例 (0, logger_js_1.debug)(`[openUrl] Checking IDE: ${currentIde}`); (0, logger_js_1.debug)(`[openUrl] mcpServer type: ${typeof mcpServer}, has server: ${!!mcpServer?.server}, has ide: ${!!mcpServer?.ide}`); if (internalServer) { (0, logger_js_1.debug)(`[openUrl] internalServer type: ${typeof internalServer}, has sendLoggingMessage: ${typeof internalServer.sendLoggingMessage === 'function'}`); } // 检查是否为 CodeBuddy IDE (优先使用 mcpServer.ide,回退到环境变量) if (currentIde === "CodeBuddy" && internalServer && typeof internalServer.sendLoggingMessage === 'function') { try { // internalServer 是 MCP server 的内部 server 实例,有 sendLoggingMessage 方法 internalServer.sendLoggingMessage({ level: "notice", data: { type: "tcb", url: url, }, }); (0, logger_js_1.info)(`CodeBuddy IDE: 已发送网页打开通知 - ${url}`); return; } catch (err) { (0, logger_js_1.error)(`Failed to send logging message for ${url}: ${err instanceof Error ? err.message : err}`, err instanceof Error ? err : new Error(String(err))); // 如果发送通知失败,在 CodeBuddy IDE 中不打开网页,直接返回 (0, logger_js_1.warn)(`CodeBuddy IDE: 发送通知失败,不打开网页 - ${url}`); return; } } // 默认行为:直接打开网页(带 VSCode/Linux 降级逻辑) (0, logger_js_1.debug)(`[openUrl] Opening URL in browser: ${url}`); try { const openOptions = options ? { url: true, ...options } : { url: true }; const child = await (0, open_1.default)(url, openOptions); if (child?.once) { child.once("error", async () => { if (shouldUseBrowserEnvFallback()) { (0, logger_js_1.debug)("[openUrl] open() failed, trying BROWSER fallback"); await openUrlByBrowserEnv(url); } }); } return; } catch (err) { (0, logger_js_1.error)(`Failed to open ${url} ${options} ${err instanceof Error ? err.message : err} `, err instanceof Error ? err : new Error(String(err))); if (shouldUseBrowserEnvFallback()) { (0, logger_js_1.debug)("[openUrl] open() threw, trying BROWSER fallback"); await openUrlByBrowserEnv(url); return; } (0, logger_js_1.warn)(`Please manually open: ${url}`); } } class InteractiveServer { app; server; wss; port = 0; isRunning = false; currentResolver = null; sessionData = new Map(); _mcpServer = null; // 保存 MCP server 实例引用 // 公共 getter 和 setter get mcpServer() { return this._mcpServer; } set mcpServer(server) { this._mcpServer = server; } DEFAULT_PORT = 3721; FALLBACK_PORTS = [ 3722, 3723, 3724, 3725, 3726, 3727, 3728, 3729, 3730, 3731, 3732, 3733, 3734, 3735, ]; /** Idle timeout for HTTP/WS connections (e.g. long login). Avoids "connection disconnected" after ~1 min. */ static SERVER_IDLE_MS = 30 * 60 * 1000; // 30 minutes /** WebSocket ping interval to keep connection alive past proxies/firewalls. */ static WS_PING_INTERVAL_MS = 25 * 1000; // 25 seconds constructor(mcpServer) { this._mcpServer = mcpServer; this.app = (0, express_1.default)(); this.server = http_1.default.createServer(this.app); this.applyServerTimeouts(); this.wss = new ws_1.WebSocketServer({ server: this.server }); this.setupExpress(); this.setupWebSocket(); process.on("exit", () => this.cleanup()); process.on("SIGINT", () => this.cleanup()); process.on("SIGTERM", () => this.cleanup()); } cleanup() { if (this.isRunning) { (0, logger_js_1.debug)("Cleaning up interactive server resources..."); this.server.close(); this.wss.close(); this.isRunning = false; } } /** Apply timeouts so long-lived login does not cause "connection disconnected". */ applyServerTimeouts() { this.server.timeout = 0; if (typeof this.server.keepAliveTimeout === "number") { this.server.keepAliveTimeout = InteractiveServer.SERVER_IDLE_MS; } if (typeof this.server.headersTimeout === "number") { this.server.headersTimeout = Math.max(this.server.headersTimeout || 0, InteractiveServer.SERVER_IDLE_MS); } } setupExpress() { this.app.use(express_1.default.json()); this.app.get("/env-setup/:sessionId", (req, res) => { const { sessionId } = req.params; const sessionData = this.sessionData.get(sessionId); if (!sessionData) { res.status(404).send("会话不存在或已过期"); return; } res.send(this.getEnvSetupHTML(sessionData.envs, sessionData.accountInfo, sessionData.errorContext, // Pass error context sessionId // Pass sessionId for retry functionality )); }); this.app.get("/clarification/:sessionId", (req, res) => { const { sessionId } = req.params; const sessionData = this.sessionData.get(sessionId); if (!sessionData) { res.status(404).send("会话不存在或已过期"); return; } res.send(this.getClarificationHTML(sessionData.message, sessionData.options)); }); this.app.post("/api/submit", (req, res) => { const { type, data } = req.body; (0, logger_js_1.debug)("Received submit request", { type, data }); if (this.currentResolver) { (0, logger_js_1.info)("Resolving with user data"); this.currentResolver({ type, data }); this.currentResolv