Skip to main content
Glama
urls_view_controller.go8.75 kB
package tui import ( "fmt" "sort" "strings" "github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/lipgloss" "github.com/standardbeagle/brummer/internal/logs" ) // URLsViewController manages the URLs view state and rendering type URLsViewController struct { urlsViewport viewport.Model ShowingMCPHelp bool // Exported for access from Model // Dependencies injected from parent Model logStore *logs.Store mcpServer MCPServerInterface width int height int headerHeight int footerHeight int } // NewURLsViewController creates a new URLs view controller func NewURLsViewController(logStore *logs.Store, mcpServer MCPServerInterface) *URLsViewController { return &URLsViewController{ urlsViewport: viewport.New(0, 0), logStore: logStore, mcpServer: mcpServer, } } // UpdateSize updates the viewport dimensions with pre-calculated content height func (v *URLsViewController) UpdateSize(width, height, headerHeight, footerHeight, contentHeight int) { v.width = width v.height = height v.headerHeight = headerHeight v.footerHeight = footerHeight v.urlsViewport.Width = width v.urlsViewport.Height = contentHeight } // ToggleMCPHelp toggles the MCP help display func (v *URLsViewController) ToggleMCPHelp() { v.ShowingMCPHelp = !v.ShowingMCPHelp } // Render renders the URLs view func (v *URLsViewController) Render() string { urls := v.logStore.GetURLs() // Apply stable sort to ensure consistent ordering in UI // Sort by: ProcessName first, then URL, then Timestamp sort.Slice(urls, func(i, j int) bool { if urls[i].ProcessName != urls[j].ProcessName { return urls[i].ProcessName < urls[j].ProcessName } if urls[i].URL != urls[j].URL { return urls[i].URL < urls[j].URL } return urls[i].Timestamp.Before(urls[j].Timestamp) }) // Separate MCP URLs from regular URLs var mcpURLs []logs.URLEntry var regularURLs []logs.URLEntry for _, urlEntry := range urls { if urlEntry.ProcessName == "MCP" || urlEntry.ProcessName == "mcp-server" { mcpURLs = append(mcpURLs, urlEntry) } else { regularURLs = append(regularURLs, urlEntry) } } // Split layout: regular URLs on left, MCP connection box on right if v.width < 100 { // For narrow screens, use simple single column return v.renderSimple(urls) } leftWidth := v.width * 2 / 3 rightWidth := v.width - leftWidth - 3 contentHeight := v.height - v.headerHeight - v.footerHeight // Create left panel content (regular URLs) var leftContent strings.Builder headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("39")) leftContent.WriteString(headerStyle.Render(fmt.Sprintf("🔗 Application URLs (%d)", len(regularURLs))) + "\n\n") if len(regularURLs) == 0 { emptyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("245")).Italic(true) leftContent.WriteString(emptyStyle.Render("No application URLs detected yet.\nStart servers with /run <script>.")) } else { leftContent.WriteString(v.renderURLsList(regularURLs)) } // Create right panel content (MCP connection box) rightContent := v.renderMCPConnectionBox(mcpURLs) // Create bordered panels leftPanel := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("240")). Width(leftWidth - 2). Height(contentHeight - 2). Padding(1). Render(leftContent.String()) rightPanel := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("75")). Width(rightWidth - 2). Height(contentHeight - 2). Padding(1). Render(rightContent) // Combine panels side by side return lipgloss.JoinHorizontal(lipgloss.Top, leftPanel, " ", rightPanel) } // renderSimple renders a simple single-column view for narrow screens func (v *URLsViewController) renderSimple(urls []logs.URLEntry) string { var content strings.Builder // URLs are already sorted by the Render method, no need to sort again // Header with count headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("39")) content.WriteString(headerStyle.Render(fmt.Sprintf("🔗 Detected URLs (%d)", len(urls))) + "\n\n") if len(urls) == 0 { emptyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("245")).Italic(true) content.WriteString(emptyStyle.Render("No URLs detected yet. Start servers with /run <script>. Use /proxy or /toggle-proxy for URL management.")) } else { content.WriteString(v.renderURLsList(urls)) } v.urlsViewport.SetContent(content.String()) return v.urlsViewport.View() } // renderURLsList renders a list of URLs with styling func (v *URLsViewController) renderURLsList(urls []logs.URLEntry) string { var content strings.Builder // Group URLs by process urlsByProcess := make(map[string][]logs.URLEntry) for _, url := range urls { urlsByProcess[url.ProcessName] = append(urlsByProcess[url.ProcessName], url) } processStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("214")) urlStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Underline(true) timeStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("102")) // Instructions instructionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("245")).Italic(true) content.WriteString(instructionStyle.Render("Press Enter on URL to copy to clipboard") + "\n\n") isFirst := true for processName, processURLs := range urlsByProcess { if !isFirst { content.WriteString("\n") } isFirst = false content.WriteString(processStyle.Render(fmt.Sprintf("📦 %s", processName)) + "\n") // Deduplicate URLs while maintaining order from sorted input seen := make(map[string]bool) for _, urlEntry := range processURLs { // Skip if we've already seen this URL if seen[urlEntry.URL] { continue } seen[urlEntry.URL] = true // Create clickable URL display clickable := fmt.Sprintf(" %s", urlStyle.Render(urlEntry.URL)) content.WriteString(clickable) // Add proxy URL if available (instead of context) if urlEntry.ProxyURL != "" { proxyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("75")).Italic(true) content.WriteString(fmt.Sprintf(" → %s", proxyStyle.Render(urlEntry.ProxyURL))) } // Add first seen time content.WriteString(fmt.Sprintf(" %s\n", timeStyle.Render(urlEntry.Timestamp.Format("15:04:05")))) } } return content.String() } // renderMCPConnectionBox renders the MCP connection information box func (v *URLsViewController) renderMCPConnectionBox(mcpURLs []logs.URLEntry) string { var content strings.Builder // MCP Connection box styling boxStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("75")). Align(lipgloss.Center) titleStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("75")). Align(lipgloss.Center) content.WriteString(titleStyle.Render("🔌 MCP Connection") + "\n\n") if len(mcpURLs) > 0 { // Show MCP is running statusStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("82")). Bold(true). Align(lipgloss.Center) content.WriteString(statusStyle.Render("✓ MCP Server Active") + "\n\n") // Show the MCP URL urlStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("39")). Underline(true) for _, url := range mcpURLs { content.WriteString(boxStyle.Render("Connect at:") + "\n") content.WriteString(urlStyle.Render(url.URL) + "\n\n") break // Only show the first one } } else { // MCP not running content.WriteString(boxStyle.Render("MCP Server Not Active") + "\n\n") } // Add connection instructions instructionStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("245")). Italic(true). Width(30). Align(lipgloss.Left) if v.ShowingMCPHelp { // Show detailed help content.WriteString(instructionStyle.Render("Claude.ai Configuration:") + "\n\n") codeStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("214")). Background(lipgloss.Color("235")). Padding(0, 1) content.WriteString(codeStyle.Render(`{ "mcpServers": { "brummer": { "command": "brum", "args": ["--mcp"], "env": {} } } }`) + "\n\n") content.WriteString(instructionStyle.Render("Save this to:") + "\n") content.WriteString(codeStyle.Render("~/Library/Application Support/Claude/claude_desktop_config.json") + "\n\n") linkStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("245")). Italic(true) content.WriteString(linkStyle.Render("Press ? to hide help")) } else { content.WriteString(instructionStyle.Render("Configure Claude.ai to connect\nto this MCP server.") + "\n\n") linkStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("75")). Bold(true) content.WriteString(linkStyle.Render("Press ? for setup help")) } return content.String() }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/standardbeagle/brummer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server