`:""}
@@ -98,34 +98,34 @@ class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatu
Token Savings Rate
${b}
-
${v}
+
${f}
ETag Cache Hit Rate
-
${u}
-
${p}
+
${g}
+
${u}
Tasks & HITL State
-
${A>0?`${A} Action Req`:`${R} Active`}
-
${A>0?"Awaiting Human-in-the-Loop decision":`${x.length} total registered tasks`}
+
${T>0?`${T} Action Req`:`${I} Active`}
+
${T>0?"Awaiting Human-in-the-Loop decision":`${x.length} total registered tasks`}
Connected Upstreams
-
${o} Active
-
${o>0?"Persistent worker task channels":"No active upstream servers"}
+
${n} Active
+
${n>0?"Persistent worker task channels":"No active upstream servers"}
- ${B}
+ ${_}
Connected Upstream Servers
-
+
- ${n}
+ ${o}
@@ -143,9 +143,9 @@ class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatu
${r}
- `}function M(e){return String(e||"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}var X=[{id:"github",name:"GitHub",category:"devtools",description:"Explore repositories, issues, pull requests, branches, and commit histories.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-github"],envFields:[{key:"GITHUB_PERSONAL_ACCESS_TOKEN",label:"GitHub Personal Access Token",placeholder:"ghp_...",required:!0,description:"Classic or fine-grained token with repo scope."}]},{id:"git",name:"Git (Local)",category:"devtools",description:"Read local Git repository status, diffs, log histories, and commit changes.",badge:"Official / uvx",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-git","--repository","."],argsPlaceholder:"--with mcp<2 mcp-server-git --repository /path/to/repo",envFields:[]},{id:"filesystem",name:"Filesystem",category:"devtools",description:"Secure, sandboxed access to local files and directories for AI workflows.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-filesystem","."],argsPlaceholder:"-y @modelcontextprotocol/server-filesystem /allowed/dir1 /allowed/dir2",envFields:[]},{id:"memory",name:"Memory Graph",category:"devtools",description:"Persistent knowledge-graph based memory for multi-turn agent learning.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-memory"],envFields:[]},{id:"chrome-devtools",name:"Chrome DevTools",category:"devtools",description:"Inspect live DOM, execute scripts, read console logs, and capture network traces in Chrome.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-puppeteer"],envFields:[]},{id:"sentry",name:"Sentry",category:"devtools",description:"Query production error events, stack traces, and issue frequencies directly from Sentry.",badge:"uvx / Telemetry",command:"uvx",defaultArgs:["--with","mcp<2","--with","httpx","mcp-server-sentry","--auth-token","sntrys_token"],argsPlaceholder:"--with mcp<2 --with httpx mcp-server-sentry --auth-token YOUR_SENTRY_TOKEN",envFields:[{key:"SENTRY_AUTH_TOKEN",label:"Sentry Auth Token",placeholder:"sntrys_...",required:!0}]},{id:"playwright",name:"Playwright Browser",category:"browser",description:"Headless / headed browser automation for scraping, form filling, and UI interaction.",badge:"Popular #1 / npx",command:"npx",defaultArgs:["-y","@executeautomation/playwright-mcp-server"],envFields:[]},{id:"puppeteer",name:"Puppeteer",category:"browser",description:"Official browser automation server for web page scraping and screenshot capture.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-puppeteer"],envFields:[]},{id:"brave-search",name:"Brave Search",category:"browser",description:"Real-time privacy-preserving web search and local point-of-interest query engine.",badge:"Official / Search",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-brave-search"],envFields:[{key:"BRAVE_API_KEY",label:"Brave Search API Key",placeholder:"BSA...",required:!0}]},{id:"tavily",name:"Tavily Search",category:"browser",description:"AI-optimized web search engine structured specifically for LLM context injection.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","tavily-mcp"],envFields:[{key:"TAVILY_API_KEY",label:"Tavily API Key",placeholder:"tvly-...",required:!0}]},{id:"fetch",name:"Fetch / Web Markdown",category:"browser",description:"Download web pages, strip clutter, and convert raw HTML to clean markdown text.",badge:"Official / uvx",command:"uvx",defaultArgs:["mcp-server-fetch"],envFields:[]},{id:"postgres",name:"PostgreSQL",category:"database",description:"Read schemas, inspect tables, and execute SQL queries against PostgreSQL databases.",badge:"Official / Database",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-postgres","postgresql://user:pass@localhost:5432/mydb"],argsPlaceholder:"-y @modelcontextprotocol/server-postgres postgresql://user:pass@localhost:5432/dbname",envFields:[]},{id:"sqlite",name:"SQLite",category:"database",description:"Local embedded SQLite query runner and schema inspector.",badge:"Official / uvx",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-sqlite","--db-path","./app.db"],argsPlaceholder:"--with mcp<2 mcp-server-sqlite --db-path /path/to/database.sqlite",envFields:[]},{id:"supabase",name:"Supabase",category:"database",description:"Query database tables, manage auth policies, and inspect storage in Supabase.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@supabase/mcp-server-supabase@latest"],envFields:[{key:"SUPABASE_ACCESS_TOKEN",label:"Supabase Personal Access Token",placeholder:"sbp_...",required:!0},{key:"SUPABASE_PROJECT_REF",label:"Supabase Project Reference ID",placeholder:"abcdefghijklmnop",required:!1}]},{id:"redis",name:"Redis",category:"database",description:"Inspect cached keys, hash sets, lists, TTLs, and pub/sub channels in Redis.",badge:"Official / Key-Value",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-redis","redis://localhost:6379"],argsPlaceholder:"-y @modelcontextprotocol/server-redis redis://localhost:6379",envFields:[]},{id:"s3",name:"AWS S3 / Cloud Storage",category:"database",description:"Browse S3 buckets, fetch object metadata, and download files from cloud storage.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","@geunoh/s3-mcp-server"],argsPlaceholder:"-y @geunoh/s3-mcp-server",envFields:[{key:"AWS_ACCESS_KEY_ID",label:"AWS Access Key ID",placeholder:"AKIA...",required:!0},{key:"AWS_SECRET_ACCESS_KEY",label:"AWS Secret Access Key",placeholder:"...",required:!0},{key:"AWS_REGION",label:"AWS Region",placeholder:"us-east-1",required:!1}]},{id:"linear",name:"Linear",category:"productivity",description:"Search, create, and triage Linear issues, cycles, teams, and project roadmaps.",badge:"Productivity / Stdio",command:"npx",defaultArgs:["-y","mcp-linear"],envFields:[{key:"LINEAR_API_KEY",label:"Linear API Key",placeholder:"lin_api_...",required:!0}]},{id:"slack",name:"Slack",category:"productivity",description:"Read channels, post messages, inspect threads, and search team discussions.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-slack"],envFields:[{key:"SLACK_BOT_TOKEN",label:"Slack Bot User Token",placeholder:"xoxb-...",required:!0},{key:"SLACK_TEAM_ID",label:"Slack Team ID",placeholder:"T01234567",required:!0}]},{id:"notion",name:"Notion",category:"productivity",description:"Search Notion workspace pages, read nested blocks, and query database entries.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@notionhq/notion-mcp-server"],envFields:[{key:"NOTION_TOKEN",label:"Notion Internal Integration Token",placeholder:"secret_...",required:!0}]},{id:"jira",name:"Jira / Atlassian",category:"productivity",description:"Manage Jira issues, search JQL, read sprint statuses, and inspect boards.",badge:"uvx / Atlassian",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-jira","--jira-base-url","https://your-domain.atlassian.net"],argsPlaceholder:"--with mcp<2 mcp-server-jira --jira-base-url https://org.atlassian.net",envFields:[{key:"JIRA_TOKEN",label:"Atlassian API Token",placeholder:"ATATT3...",required:!0}]},{id:"google-drive",name:"Google Drive",category:"productivity",description:"Search, list, and read documents, spreadsheets, and drive files.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","@piotr-agier/google-drive-mcp"],envFields:[{key:"GOOGLE_APPLICATION_CREDENTIALS",label:"Google Credentials JSON Path",placeholder:"/path/to/credentials.json",required:!0}]},{id:"docker",name:"Docker",category:"cloud",description:"Inspect running containers, tail container logs, list images, and manage compose services.",badge:"uvx / DevOps",command:"uvx",defaultArgs:["mcp-server-docker"],envFields:[]},{id:"kubernetes",name:"Kubernetes (K8s)",category:"cloud",description:"Query cluster pods, services, deployment status, and inspect Kubernetes logs.",badge:"Popular / Stdio",command:"npx",defaultArgs:["-y","@strowk/mcp-k8s"],envFields:[{key:"KUBECONFIG",label:"Kubeconfig File Path (Optional)",placeholder:"~/.kube/config",required:!1}]},{id:"cloudflare",name:"Cloudflare",category:"cloud",description:"Manage Cloudflare Workers, KV namespaces, D1 databases, Vectorize indexes, and DNS.",badge:"Official / Cloudflare",command:"npx",defaultArgs:["-y","@cloudflare/mcp-server-cloudflare","run","dummy_account_id"],argsPlaceholder:"-y @cloudflare/mcp-server-cloudflare run YOUR_ACCOUNT_ID",envFields:[{key:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",placeholder:"...",required:!0},{key:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",placeholder:"...",required:!0}]},{id:"terraform",name:"Terraform",category:"cloud",description:"Inspect Terraform state files, resource dependency graphs, and plan previews.",badge:"Community / IaC",command:"npx",defaultArgs:["-y","@mseep/terraform-mcp-server"],envFields:[]}];function Q(e,t,a){let o=X.find((r)=>r.id.toLowerCase()===e.toLowerCase());if(o)return o;let n=`${t||""} ${(a||[]).join(" ")}`.toLowerCase();return X.find((r)=>{let s=`${r.command} ${r.defaultArgs.join(" ")}`.toLowerCase();if(n.includes(r.id.toLowerCase()))return!0;if(r.command&&n.includes(r.command.toLowerCase())&&r.defaultArgs.some((i)=>n.includes(i.toLowerCase())))return!0;return!1})}function ie(){let e=c.getState(),t=e.config.mcpServers||{},a=Object.keys(t),o=e.activeProfile,n=o?e.config.profiles?.[o]:void 0,r=!!n,s=n?.servers||[],i="";if(a.length===0)i=`
+ `}var D=[{id:"github",name:"GitHub",category:"devtools",description:"Explore repositories, issues, pull requests, branches, and commit histories.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-github"],envFields:[{key:"GITHUB_PERSONAL_ACCESS_TOKEN",label:"GitHub Personal Access Token",placeholder:"ghp_...",required:!0,description:"Classic or fine-grained token with repo scope."}]},{id:"git",name:"Git (Local)",category:"devtools",description:"Read local Git repository status, diffs, log histories, and commit changes.",badge:"Official / uvx",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-git","--repository","."],argsPlaceholder:"--with mcp<2 mcp-server-git --repository /path/to/repo",envFields:[]},{id:"filesystem",name:"Filesystem",category:"devtools",description:"Secure, sandboxed access to local files and directories for AI workflows.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-filesystem","."],argsPlaceholder:"-y @modelcontextprotocol/server-filesystem /allowed/dir1 /allowed/dir2",envFields:[]},{id:"memory",name:"Memory Graph",category:"devtools",description:"Persistent knowledge-graph based memory for multi-turn agent learning.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-memory"],envFields:[]},{id:"chrome-devtools",name:"Chrome DevTools",category:"devtools",description:"Inspect live DOM, execute scripts, read console logs, and capture network traces in Chrome.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-puppeteer"],envFields:[]},{id:"sentry",name:"Sentry",category:"devtools",description:"Query production error events, stack traces, and issue frequencies directly from Sentry.",badge:"uvx / Telemetry",command:"uvx",defaultArgs:["--with","mcp<2","--with","httpx","mcp-server-sentry","--auth-token","sntrys_token"],argsPlaceholder:"--with mcp<2 --with httpx mcp-server-sentry --auth-token YOUR_SENTRY_TOKEN",envFields:[{key:"SENTRY_AUTH_TOKEN",label:"Sentry Auth Token",placeholder:"sntrys_...",required:!0}]},{id:"playwright",name:"Playwright Browser",category:"browser",description:"Headless / headed browser automation for scraping, form filling, and UI interaction.",badge:"Popular #1 / npx",command:"npx",defaultArgs:["-y","@executeautomation/playwright-mcp-server"],envFields:[]},{id:"puppeteer",name:"Puppeteer",category:"browser",description:"Official browser automation server for web page scraping and screenshot capture.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-puppeteer"],envFields:[]},{id:"brave-search",name:"Brave Search",category:"browser",description:"Real-time privacy-preserving web search and local point-of-interest query engine.",badge:"Official / Search",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-brave-search"],envFields:[{key:"BRAVE_API_KEY",label:"Brave Search API Key",placeholder:"BSA...",required:!0}]},{id:"tavily",name:"Tavily Search",category:"browser",description:"AI-optimized web search engine structured specifically for LLM context injection.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","tavily-mcp"],envFields:[{key:"TAVILY_API_KEY",label:"Tavily API Key",placeholder:"tvly-...",required:!0}]},{id:"fetch",name:"Fetch / Web Markdown",category:"browser",description:"Download web pages, strip clutter, and convert raw HTML to clean markdown text.",badge:"Official / uvx",command:"uvx",defaultArgs:["mcp-server-fetch"],envFields:[]},{id:"postgres",name:"PostgreSQL",category:"database",description:"Read schemas, inspect tables, and execute SQL queries against PostgreSQL databases.",badge:"Official / Database",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-postgres","postgresql://user:pass@localhost:5432/mydb"],argsPlaceholder:"-y @modelcontextprotocol/server-postgres postgresql://user:pass@localhost:5432/dbname",envFields:[]},{id:"sqlite",name:"SQLite",category:"database",description:"Local embedded SQLite query runner and schema inspector.",badge:"Official / uvx",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-sqlite","--db-path","./app.db"],argsPlaceholder:"--with mcp<2 mcp-server-sqlite --db-path /path/to/database.sqlite",envFields:[]},{id:"supabase",name:"Supabase",category:"database",description:"Query database tables, manage auth policies, and inspect storage in Supabase.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@supabase/mcp-server-supabase@latest"],envFields:[{key:"SUPABASE_ACCESS_TOKEN",label:"Supabase Personal Access Token",placeholder:"sbp_...",required:!0},{key:"SUPABASE_PROJECT_REF",label:"Supabase Project Reference ID",placeholder:"abcdefghijklmnop",required:!1}]},{id:"redis",name:"Redis",category:"database",description:"Inspect cached keys, hash sets, lists, TTLs, and pub/sub channels in Redis.",badge:"Official / Key-Value",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-redis","redis://localhost:6379"],argsPlaceholder:"-y @modelcontextprotocol/server-redis redis://localhost:6379",envFields:[]},{id:"s3",name:"AWS S3 / Cloud Storage",category:"database",description:"Browse S3 buckets, fetch object metadata, and download files from cloud storage.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","@geunoh/s3-mcp-server"],argsPlaceholder:"-y @geunoh/s3-mcp-server",envFields:[{key:"AWS_ACCESS_KEY_ID",label:"AWS Access Key ID",placeholder:"AKIA...",required:!0},{key:"AWS_SECRET_ACCESS_KEY",label:"AWS Secret Access Key",placeholder:"...",required:!0},{key:"AWS_REGION",label:"AWS Region",placeholder:"us-east-1",required:!1}]},{id:"linear",name:"Linear",category:"productivity",description:"Search, create, and triage Linear issues, cycles, teams, and project roadmaps.",badge:"Productivity / Stdio",command:"npx",defaultArgs:["-y","mcp-linear"],envFields:[{key:"LINEAR_API_KEY",label:"Linear API Key",placeholder:"lin_api_...",required:!0}]},{id:"slack",name:"Slack",category:"productivity",description:"Read channels, post messages, inspect threads, and search team discussions.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-slack"],envFields:[{key:"SLACK_BOT_TOKEN",label:"Slack Bot User Token",placeholder:"xoxb-...",required:!0},{key:"SLACK_TEAM_ID",label:"Slack Team ID",placeholder:"T01234567",required:!0}]},{id:"notion",name:"Notion",category:"productivity",description:"Search Notion workspace pages, read nested blocks, and query database entries.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@notionhq/notion-mcp-server"],envFields:[{key:"NOTION_TOKEN",label:"Notion Internal Integration Token",placeholder:"secret_...",required:!0}]},{id:"jira",name:"Jira / Atlassian",category:"productivity",description:"Manage Jira issues, search JQL, read sprint statuses, and inspect boards.",badge:"uvx / Atlassian",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-jira","--jira-base-url","https://your-domain.atlassian.net"],argsPlaceholder:"--with mcp<2 mcp-server-jira --jira-base-url https://org.atlassian.net",envFields:[{key:"JIRA_TOKEN",label:"Atlassian API Token",placeholder:"ATATT3...",required:!0}]},{id:"google-drive",name:"Google Drive",category:"productivity",description:"Search, list, and read documents, spreadsheets, and drive files.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","@piotr-agier/google-drive-mcp"],envFields:[{key:"GOOGLE_APPLICATION_CREDENTIALS",label:"Google Credentials JSON Path",placeholder:"/path/to/credentials.json",required:!0}]},{id:"docker",name:"Docker",category:"cloud",description:"Inspect running containers, tail container logs, list images, and manage compose services.",badge:"uvx / DevOps",command:"uvx",defaultArgs:["mcp-server-docker"],envFields:[]},{id:"kubernetes",name:"Kubernetes (K8s)",category:"cloud",description:"Query cluster pods, services, deployment status, and inspect Kubernetes logs.",badge:"Popular / Stdio",command:"npx",defaultArgs:["-y","@strowk/mcp-k8s"],envFields:[{key:"KUBECONFIG",label:"Kubeconfig File Path (Optional)",placeholder:"~/.kube/config",required:!1}]},{id:"cloudflare",name:"Cloudflare",category:"cloud",description:"Manage Cloudflare Workers, KV namespaces, D1 databases, Vectorize indexes, and DNS.",badge:"Official / Cloudflare",command:"npx",defaultArgs:["-y","@cloudflare/mcp-server-cloudflare","run","dummy_account_id"],argsPlaceholder:"-y @cloudflare/mcp-server-cloudflare run YOUR_ACCOUNT_ID",envFields:[{key:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",placeholder:"...",required:!0},{key:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",placeholder:"...",required:!0}]},{id:"terraform",name:"Terraform",category:"cloud",description:"Inspect Terraform state files, resource dependency graphs, and plan previews.",badge:"Community / IaC",command:"npx",defaultArgs:["-y","@mseep/terraform-mcp-server"],envFields:[]}];function N(e,t,a){let n=D.find((r)=>r.id.toLowerCase()===e.toLowerCase());if(n)return n;let o=`${t||""} ${(a||[]).join(" ")}`.toLowerCase();return D.find((r)=>{let s=`${r.command} ${r.defaultArgs.join(" ")}`.toLowerCase();if(o.includes(r.id.toLowerCase()))return!0;if(r.command&&o.includes(r.command.toLowerCase())&&r.defaultArgs.some((l)=>o.includes(l.toLowerCase())))return!0;return!1})}function X(){let e=p.getState(),t=e.config.mcpServers||{},a=Object.keys(t),n=e.activeProfile,o=n?e.config.profiles?.[n]:void 0,r=!!o,s=o?.servers||[],l="";if(a.length===0)l=`
-
No Servers Configured in ${I(e.configPath)}
+
No Servers Configured in ${i(e.configPath)}
Warmplane bridges local tools and remote MCP servers into one unified facade. Add your first server or import existing configs from Claude Desktop or Cursor.
@@ -155,56 +155,56 @@ class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatu
- ${k(o?o.summary||o.description:"Connect servers to inspect and execute tools")}
+ ${i(n?n.summary||n.description:"Connect servers to inspect and execute tools")}
- ${n?`
+ ${o?`
@@ -449,7 +449,7 @@ class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatu
Execution suspended for Human-in-the-Loop approval or async resolution.
@@ -556,25 +556,25 @@ class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatu
`:""}
-
${e.executionResult?k(JSON.stringify(e.executionResult.data,null,2)):"// Response envelope output will be formatted here"}
+
${e.executionResult?i(JSON.stringify(e.executionResult.data,null,2)):"// Response envelope output will be formatted here"}
- ${k(n?n.description||n.title||"Bind variables and render messages":"Select a prompt from the list to test")}
+ ${i(o?o.description||o.title||"Bind variables and render messages":"Select a prompt from the list to test")}
✓
@@ -862,7 +862,7 @@ class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatu
Tool calls requiring Human-in-the-Loop approval or returning asynchronous input_required tasks will suspend here for operator inspection, parameter editing, and response submission.
- Viewing & Editing Policy for Profile Constellation: ${j(t)}
+ Viewing & Editing Policy for Profile Constellation: ${i(t)}
Rules defined here apply specifically when requests target this profile. Deny and HITL rules are strictly additive with global rules.
@@ -1484,11 +1484,11 @@ class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatu
Global security policy rules governing wildcard access control, human-in-the-loop triggers, and sensitive key masking. (Select an active profile in the top bar to edit per-profile rules).
- Constellation Server Boundaries (Profile: ${j(t)})
- ${x.length} of ${v.length} servers active
+ Constellation Server Boundaries (Profile: ${i(t)})
+ ${x.length} of ${f.length} servers active
@@ -1496,9 +1496,9 @@ class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatu
✔ Included Servers (${x.length})
- No facade aliases configured in ${P(e.configPath)}. Add short names or custom descriptions to prune token payload sizes.
+ No facade aliases configured in ${i(e.configPath)}. Add short names or custom descriptions to prune token payload sizes.
${y?"\uD83D\uDCE6 Output Result Payload":"State Details"}
-
${f(y?JSON.stringify(s.result,null,2):s.status==="working"?"Task execution is currently in-flight in background worker pool.":"No output payload recorded.")}
+
${i(y?JSON.stringify(s.result,null,2):s.status==="working"?"Task execution is currently in-flight in background worker pool.":"No output payload recorded.")}
- `}r.classList.add("active")}openAddServerModal(){this.closeModals();let e=document.getElementById("modal-srv-title"),t=document.getElementById("modal-srv-template-banner"),a=document.getElementById("modal-srv-name"),o=document.getElementById("modal-srv-transport"),n=document.getElementById("modal-srv-command"),r=document.getElementById("modal-srv-url"),s=document.getElementById("modal-srv-ft"),i=document.getElementById("modal-srv-cd"),l=document.getElementById("modal-srv-autorestart"),u=document.getElementById("modal-srv-maxrestarts");if(e)e.textContent="Add Upstream MCP Server";if(t)t.style.display="flex";if(a)a.value="",a.disabled=!1;if(o)o.value="stdio";if(n)n.value="";if(r)r.value="";let p=document.getElementById("modal-group-cmd"),g=document.getElementById("modal-group-url");if(p)p.style.display="block";if(g)g.style.display="none";if(s)s.value="3";if(i)i.value="30000";if(l)l.value="true";if(u)u.value="5";let m=document.getElementById("modal-add-server");if(m)m.classList.add("active")}openEditServerModal(e){this.closeModals();let t=c.getState(),a=t.config.mcpServers?.[e];if(!a){alert(`Server '${e}' not found in configuration.`);return}let o=document.getElementById("modal-srv-title"),n=document.getElementById("modal-srv-template-banner"),r=document.getElementById("modal-srv-name"),s=document.getElementById("modal-srv-transport"),i=document.getElementById("modal-srv-command"),l=document.getElementById("modal-srv-url"),u=document.getElementById("modal-srv-ft"),p=document.getElementById("modal-srv-cd"),g=document.getElementById("modal-srv-autorestart"),m=document.getElementById("modal-srv-maxrestarts");if(o)o.textContent=`Edit Server '${e}'`;if(n)n.style.display="none";if(r)r.value=e,r.disabled=!0;let d=!!a.command;if(s)s.value=d?"stdio":"http";let y=document.getElementById("modal-group-cmd"),b=document.getElementById("modal-group-url");if(y)y.style.display=d?"block":"none";if(b)b.style.display=d?"none":"block";if(i)i.value=d?`${a.command} ${(a.args||[]).join(" ")}`.trim():"";if(l)l.value=a.url||"";let v=a.resilience||t.config.resilience;if(u)u.value=String(v?.failureThreshold??3);if(p)p.value=String(v?.cooldownMs??30000);if(g)g.value=v?.autoRestart===!1?"false":"true";if(m)m.value=String(v?.maxRestarts??5);let x=document.getElementById("modal-add-server");if(x)x.classList.add("active")}async submitAddServer(){let e=document.getElementById("modal-srv-name"),t=e?.value.trim(),a=document.getElementById("modal-srv-transport")?.value;if(!t){alert("Server name is required");return}if(e&&!e.disabled){if((c.getState().config.mcpServers||{})[t]){if(!confirm(`Server '${t}' already exists in configuration. Do you want to overwrite it?`))return}}let o={};if(a==="stdio"){let p=(document.getElementById("modal-srv-command")?.value.trim()).split(/\s+/).filter(Boolean);if(p.length===0){alert("Command is required");return}o.command=p[0],o.args=p.slice(1)}else{let u=document.getElementById("modal-srv-url")?.value.trim();if(!u){alert("URL is required");return}o.url=u}let n=document.getElementById("modal-srv-ft")?.value.trim(),r=document.getElementById("modal-srv-cd")?.value.trim(),s=document.getElementById("modal-srv-autorestart")?.value,i=document.getElementById("modal-srv-maxrestarts")?.value.trim();if(n||r||s||i)o.resilience={failureThreshold:n?Number(n):3,cooldownMs:r?Number(r):30000,autoRestart:s!=="false",maxRestarts:i?Number(i):5};let l=await h.upsertServer(t,o);if(l.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save server: ${l.error}`)}openTemplateCatalog(){this.closeModals();let e=document.getElementById("modal-templates");if(e)e.classList.add("active");this.renderTemplateGrid()}setTemplateCategory(e){this.activeTemplateCategory=e,document.querySelectorAll(".tmpl-cat-btn").forEach((t)=>{if(t.getAttribute("data-category")===e)t.classList.add("active"),t.style.background="var(--surface-elevated)",t.style.color="var(--amber-400)";else t.classList.remove("active"),t.style.background="var(--surface-card)",t.style.color="var(--text-main)"}),this.renderTemplateGrid()}filterTemplates(e){this.activeTemplateFilter=e.toLowerCase().trim(),this.renderTemplateGrid()}renderTemplateGrid(){let e=document.getElementById("tmpl-grid");if(!e)return;let t=X.filter((n)=>{let r=this.activeTemplateCategory==="all"||n.category===this.activeTemplateCategory,s=!this.activeTemplateFilter||n.name.toLowerCase().includes(this.activeTemplateFilter)||n.id.toLowerCase().includes(this.activeTemplateFilter)||n.description.toLowerCase().includes(this.activeTemplateFilter)||n.command.toLowerCase().includes(this.activeTemplateFilter)||n.envFields.some((i)=>i.key.toLowerCase().includes(this.activeTemplateFilter));return r&&s});if(t.length===0){e.innerHTML=`
+ `}r.classList.add("active")}openAddServerModal(){this.closeModals();let e=document.getElementById("modal-srv-title"),t=document.getElementById("modal-srv-template-banner"),a=document.getElementById("modal-srv-name"),n=document.getElementById("modal-srv-transport"),o=document.getElementById("modal-srv-command"),r=document.getElementById("modal-srv-url"),s=document.getElementById("modal-srv-ft"),l=document.getElementById("modal-srv-cd"),d=document.getElementById("modal-srv-autorestart"),g=document.getElementById("modal-srv-maxrestarts");if(e)e.textContent="Add Upstream MCP Server";if(t)t.style.display="flex";if(a)a.value="",a.disabled=!1;if(n)n.value="stdio";if(o)o.value="";if(r)r.value="";let u=document.getElementById("modal-group-cmd"),m=document.getElementById("modal-group-url");if(u)u.style.display="block";if(m)m.style.display="none";if(s)s.value="3";if(l)l.value="30000";if(d)d.value="true";if(g)g.value="5";let v=document.getElementById("modal-add-server");if(v)v.classList.add("active")}openEditServerModal(e){this.closeModals();let t=p.getState(),a=t.config.mcpServers?.[e];if(!a){alert(`Server '${e}' not found in configuration.`);return}let n=document.getElementById("modal-srv-title"),o=document.getElementById("modal-srv-template-banner"),r=document.getElementById("modal-srv-name"),s=document.getElementById("modal-srv-transport"),l=document.getElementById("modal-srv-command"),d=document.getElementById("modal-srv-url"),g=document.getElementById("modal-srv-ft"),u=document.getElementById("modal-srv-cd"),m=document.getElementById("modal-srv-autorestart"),v=document.getElementById("modal-srv-maxrestarts");if(n)n.textContent=`Edit Server '${e}'`;if(o)o.style.display="none";if(r)r.value=e,r.disabled=!0;let c=!!a.command;if(s)s.value=c?"stdio":"http";let y=document.getElementById("modal-group-cmd"),b=document.getElementById("modal-group-url");if(y)y.style.display=c?"block":"none";if(b)b.style.display=c?"none":"block";if(l)l.value=c?`${a.command} ${(a.args||[]).join(" ")}`.trim():"";if(d)d.value=a.url||"";let f=a.resilience||t.config.resilience;if(g)g.value=String(f?.failureThreshold??3);if(u)u.value=String(f?.cooldownMs??30000);if(m)m.value=f?.autoRestart===!1?"false":"true";if(v)v.value=String(f?.maxRestarts??5);let x=document.getElementById("modal-add-server");if(x)x.classList.add("active")}async submitAddServer(){let e=document.getElementById("modal-srv-name"),t=e?.value.trim(),a=document.getElementById("modal-srv-transport")?.value;if(!t){alert("Server name is required");return}if(e&&!e.disabled){if((p.getState().config.mcpServers||{})[t]){if(!confirm(`Server '${t}' already exists in configuration. Do you want to overwrite it?`))return}}let n={};if(a==="stdio"){let u=(document.getElementById("modal-srv-command")?.value.trim()).split(/\s+/).filter(Boolean);if(u.length===0){alert("Command is required");return}n.command=u[0],n.args=u.slice(1)}else{let g=document.getElementById("modal-srv-url")?.value.trim();if(!g){alert("URL is required");return}n.url=g}let o=document.getElementById("modal-srv-ft")?.value.trim(),r=document.getElementById("modal-srv-cd")?.value.trim(),s=document.getElementById("modal-srv-autorestart")?.value,l=document.getElementById("modal-srv-maxrestarts")?.value.trim();if(o||r||s||l)n.resilience={failureThreshold:o?Number(o):3,cooldownMs:r?Number(r):30000,autoRestart:s!=="false",maxRestarts:l?Number(l):5};let d=await h.upsertServer(t,n);if(d.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save server: ${d.error}`)}openTemplateCatalog(){this.closeModals();let e=document.getElementById("modal-templates");if(e)e.classList.add("active");this.renderTemplateGrid()}setTemplateCategory(e){this.activeTemplateCategory=e,document.querySelectorAll(".tmpl-cat-btn").forEach((t)=>{if(t.getAttribute("data-category")===e)t.classList.add("active"),t.style.background="var(--surface-elevated)",t.style.color="var(--amber-400)";else t.classList.remove("active"),t.style.background="var(--surface-card)",t.style.color="var(--text-main)"}),this.renderTemplateGrid()}filterTemplates(e){this.activeTemplateFilter=e.toLowerCase().trim(),this.renderTemplateGrid()}renderTemplateGrid(){let e=document.getElementById("tmpl-grid");if(!e)return;let t=D.filter((o)=>{let r=this.activeTemplateCategory==="all"||o.category===this.activeTemplateCategory,s=!this.activeTemplateFilter||o.name.toLowerCase().includes(this.activeTemplateFilter)||o.id.toLowerCase().includes(this.activeTemplateFilter)||o.description.toLowerCase().includes(this.activeTemplateFilter)||o.command.toLowerCase().includes(this.activeTemplateFilter)||o.envFields.some((l)=>l.key.toLowerCase().includes(this.activeTemplateFilter));return r&&s});if(t.length===0){e.innerHTML=`
- `}}async submitTemplateServer(){if(!this.selectedTemplate)return;let e=this.selectedTemplate,t=document.getElementById("cfg-srv-id")?.value.trim(),a=document.getElementById("cfg-srv-args")?.value.trim();if(!t){alert("Server identifier is required");return}if((c.getState().config.mcpServers||{})[t]){if(!confirm(`Server '${t}' already exists. Do you want to overwrite its configuration?`))return}let r=a?a.split(/\s+/).filter(Boolean):[],s={},i=document.querySelectorAll(".tmpl-env-input");for(let y of Array.from(i)){let b=y.getAttribute("data-key"),v=y.value.trim(),x=e.envFields.find((A)=>A.key===b);if(x?.required&&!v){alert(`Required field '${x.label}' is missing.`);return}if(b&&v)s[b]=v}let l={command:e.command,args:r};if(Object.keys(s).length>0)l.env=s;let u=document.getElementById("cfg-srv-ft")?.value.trim(),p=document.getElementById("cfg-srv-cd")?.value.trim(),g=document.getElementById("cfg-srv-autorestart")?.value,m=document.getElementById("cfg-srv-maxrestarts")?.value.trim();if(u||p||g||m)l.resilience={failureThreshold:u?Number(u):3,cooldownMs:p?Number(p):30000,autoRestart:g!=="false",maxRestarts:m?Number(m):5};let d=await h.upsertServer(t,l);if(d.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save server: ${d.error}`)}async openImportModal(){this.closeModals();let e=document.getElementById("modal-import");if(e)e.classList.add("active");let t=document.getElementById("modal-eco-list");if(!t)return;t.innerHTML='
Scanning IDE configs...
';try{let a=await h.getEcosystemSources();if(a.sources&&a.sources.length>0)t.innerHTML=a.sources.map((o)=>`
+ `}}async submitTemplateServer(){if(!this.selectedTemplate)return;let e=this.selectedTemplate,t=document.getElementById("cfg-srv-id")?.value.trim(),a=document.getElementById("cfg-srv-args")?.value.trim();if(!t){alert("Server identifier is required");return}if((p.getState().config.mcpServers||{})[t]){if(!confirm(`Server '${t}' already exists. Do you want to overwrite its configuration?`))return}let r=a?a.split(/\s+/).filter(Boolean):[],s={},l=document.querySelectorAll(".tmpl-env-input");for(let y of Array.from(l)){let b=y.getAttribute("data-key"),f=y.value.trim(),x=e.envFields.find((T)=>T.key===b);if(x?.required&&!f){alert(`Required field '${x.label}' is missing.`);return}if(b&&f)s[b]=f}let d={command:e.command,args:r};if(Object.keys(s).length>0)d.env=s;let g=document.getElementById("cfg-srv-ft")?.value.trim(),u=document.getElementById("cfg-srv-cd")?.value.trim(),m=document.getElementById("cfg-srv-autorestart")?.value,v=document.getElementById("cfg-srv-maxrestarts")?.value.trim();if(g||u||m||v)d.resilience={failureThreshold:g?Number(g):3,cooldownMs:u?Number(u):30000,autoRestart:m!=="false",maxRestarts:v?Number(v):5};let c=await h.upsertServer(t,d);if(c.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save server: ${c.error}`)}async openImportModal(){this.closeModals();let e=document.getElementById("modal-import");if(e)e.classList.add("active");let t=document.getElementById("modal-eco-list");if(!t)return;t.innerHTML='
No external MCP configuration files found on this system.
'}catch{t.innerHTML='
Failed to scan ecosystem sources.
'}}async submitImport(){let e=document.querySelectorAll(".eco-checkbox:checked");if(e.length===0){alert("No sources selected");return}for(let t of Array.from(e))await h.importConfig(t.value,!1);this.closeModals(),await this.refreshData()}async refreshClients(){try{let e=await h.getClients();if(e.ok&&Array.isArray(e.clients))c.setState({clients:e.clients})}catch(e){console.error("Failed to scan clients:",e)}}setClientCategoryFilter(e){c.setState({clientFilterCategory:e})}setClientSearchQuery(e){c.setState({clientSearchQuery:e})}async attachClient(e,t){let a=t;if(!a){let o=document.getElementById(`client-prof-${e}`)||document.getElementById(`overview-client-prof-${e}`);if(o)a=o.value||void 0;else a=c.getState().activeProfile||void 0}this.openClientAttachModal(e,a)}openClientAttachModal(e,t){this.closeModals(),this.pendingClientId=e;let a=c.getState().config,o=a.mcpHttpServer,n=o?`http://${o.bind==="0.0.0.0"||o.bind==="::"?"127.0.0.1":o.bind||"127.0.0.1"}:${o.port||9191}/mcp`:"",r=document.getElementById("modal-client-title"),s=document.getElementById("modal-client-transport"),i=document.getElementById("modal-client-url"),l=document.getElementById("modal-client-profile"),u=document.getElementById("modal-client-profile-wrap");if(r)r.textContent=`Connect ${e}`;if(s)s.value="stdio";if(i)i.value=n;if(l){let p=Object.keys(a.profiles||{});if(l.innerHTML=`${p.map((g)=>``).join("")}`,l.value=t||"",u)u.style.display=p.length>0?"block":"none"}this.updateClientTransportForm(),document.getElementById("modal-client-attach")?.classList.add("active")}updateClientTransportForm(){let t=(document.getElementById("modal-client-transport")?.value||"stdio")==="http",a=document.getElementById("modal-client-http-group"),o=document.getElementById("modal-client-http-status"),n=!!c.getState().config.mcpHttpServer;if(a)a.style.display=t?"block":"none";if(o)o.textContent=n?"Uses the daemon Streamable HTTP endpoint. The daemon must already be running.":"HTTP is unavailable until mcpHttpServer is configured and the daemon is restarted.",o.style.color=n?"var(--text-dim)":"var(--amber-300)"}async submitClientAttach(){let e=this.pendingClientId;if(!e)return;let t=document.getElementById("modal-client-transport")?.value||"stdio",a=document.getElementById("modal-client-profile")?.value||void 0,o=document.getElementById("modal-client-url")?.value.trim()||void 0;if(t==="http"&&!c.getState().config.mcpHttpServer&&!o){alert("Configure mcpHttpServer and restart the daemon, or provide an explicit HTTP endpoint.");return}if(t==="http"&&!o){alert("Enter an HTTP endpoint URL.");return}let n=await h.attachClient(e,a,t,o);if(!n.ok)alert(`Failed to attach client: ${n.error||n.message||"Unknown error"}`);else this.pendingClientId=null,this.closeModals(),await this.refreshData()}async detachClient(e){if(!confirm("Disconnect Warmplane from this client?"))return;let t=await h.detachClient(e);if(!t.ok)alert(`Failed to detach client: ${t.error||t.message||"Unknown error"}`);else await this.refreshData()}handleAliasTargetInput(e){let t=document.getElementById("alias-suggestions-dropdown");if(!t)return;let a=(e||"").trim().toLowerCase();if(a.length<2){t.style.display="none";return}let n=c.getState().capabilities.filter((r)=>r.id.toLowerCase().includes(a)||r.summary&&r.summary.toLowerCase().includes(a)||r.description&&r.description.toLowerCase().includes(a)||r.server&&r.server.toLowerCase().includes(a)).slice(0,8);if(n.length===0){t.style.display="none";return}t.innerHTML=n.map((r)=>`
+ `).join("");else t.innerHTML='
No external MCP configuration files found on this system.
'}catch{t.innerHTML='
Failed to scan ecosystem sources.
'}}async submitImport(){let e=document.querySelectorAll(".eco-checkbox:checked");if(e.length===0){alert("No sources selected");return}for(let t of Array.from(e))await h.importConfig(t.value,!1);this.closeModals(),await this.refreshData()}async refreshClients(){try{let e=await h.getClients();if(e.ok&&Array.isArray(e.clients))p.setState({clients:e.clients})}catch(e){console.error("Failed to scan clients:",e)}}setClientCategoryFilter(e){p.setState({clientFilterCategory:e})}setClientSearchQuery(e){p.setState({clientSearchQuery:e})}async attachClient(e,t){let a=t;if(!a){let n=document.getElementById(`client-prof-${e}`)||document.getElementById(`overview-client-prof-${e}`);if(n)a=n.value||void 0;else a=p.getState().activeProfile||void 0}this.openClientAttachModal(e,a)}openClientAttachModal(e,t){this.closeModals(),this.pendingClientId=e;let a=p.getState().config,n=a.mcpHttpServer,o=n?`http://${n.bind==="0.0.0.0"||n.bind==="::"?"127.0.0.1":n.bind||"127.0.0.1"}:${n.port||9191}/mcp`:"",r=document.getElementById("modal-client-title"),s=document.getElementById("modal-client-transport"),l=document.getElementById("modal-client-url"),d=document.getElementById("modal-client-profile"),g=document.getElementById("modal-client-profile-wrap");if(r)r.textContent=`Connect ${e}`;if(s)s.value="stdio";if(l)l.value=o;if(d){let u=Object.keys(a.profiles||{});if(d.innerHTML=`${u.map((m)=>``).join("")}`,d.value=t||"",g)g.style.display=u.length>0?"block":"none"}this.updateClientTransportForm(),document.getElementById("modal-client-attach")?.classList.add("active")}updateClientTransportForm(){let t=(document.getElementById("modal-client-transport")?.value||"stdio")==="http",a=document.getElementById("modal-client-http-group"),n=document.getElementById("modal-client-http-status"),o=!!p.getState().config.mcpHttpServer;if(a)a.style.display=t?"block":"none";if(n)n.textContent=o?"Uses the daemon Streamable HTTP endpoint. The daemon must already be running.":"HTTP is unavailable until mcpHttpServer is configured and the daemon is restarted.",n.style.color=o?"var(--text-dim)":"var(--amber-300)"}async submitClientAttach(){let e=this.pendingClientId;if(!e)return;let t=document.getElementById("modal-client-transport")?.value||"stdio",a=document.getElementById("modal-client-profile")?.value||void 0,n=document.getElementById("modal-client-url")?.value.trim()||void 0;if(t==="http"&&!p.getState().config.mcpHttpServer&&!n){alert("Configure mcpHttpServer and restart the daemon, or provide an explicit HTTP endpoint.");return}if(t==="http"&&!n){alert("Enter an HTTP endpoint URL.");return}let o=await h.attachClient(e,a,t,n);if(!o.ok)alert(`Failed to attach client: ${o.error||o.message||"Unknown error"}`);else this.pendingClientId=null,this.closeModals(),await this.refreshData()}async detachClient(e){if(!confirm("Disconnect Warmplane from this client?"))return;let t=await h.detachClient(e);if(!t.ok)alert(`Failed to detach client: ${t.error||t.message||"Unknown error"}`);else await this.refreshData()}handleAliasTargetInput(e){let t=document.getElementById("alias-suggestions-dropdown");if(!t)return;let a=(e||"").trim().toLowerCase();if(a.length<2){t.style.display="none";return}let o=p.getState().capabilities.filter((r)=>r.id.toLowerCase().includes(a)||r.summary&&r.summary.toLowerCase().includes(a)||r.description&&r.description.toLowerCase().includes(a)||r.server&&r.server.toLowerCase().includes(a)).slice(0,8);if(o.length===0){t.style.display="none";return}t.innerHTML=o.map((r)=>`
';return}t.innerHTML=n.map((o)=>{let r=e.includes(o)?"checked":"";return`
- `}).join("")}async saveProfile(){let e=document.getElementById("modal-prof-name"),t=document.getElementById("modal-prof-desc"),a=e?.value.trim(),o=t?.value.trim();if(!a){alert("Please enter a profile name");return}let n=document.querySelectorAll(".prof-server-checkbox:checked"),r=[];if(n.forEach((v)=>{r.push(v.value)}),r.length===0){alert("Please select at least one server to include in this constellation");return}let s=(v)=>{if(!v)return[];return v.split(",").map((x)=>x.trim()).filter((x)=>x.length>0)},i=document.getElementById("modal-prof-allow"),l=document.getElementById("modal-prof-deny"),u=document.getElementById("modal-prof-hitl"),p=document.getElementById("modal-prof-redact"),g=s(i?.value),m=s(l?.value),d=s(u?.value),y=s(p?.value),b=void 0;if(g.length>0||m.length>0||d.length>0||y.length>0)b={allow:g,deny:m,requireApproval:d,redactKeys:y};try{let v=await h.upsertProfile(a,r,o||void 0,b);if(v.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save profile: ${v.error||"Unknown error"}`)}catch(v){alert(`Error saving profile: ${v.message}`)}}async deleteProfile(e){if(!confirm(`Are you sure you want to delete profile '${e}'?`))return;try{let t=await h.deleteProfile(e);if(t.ok){if(c.getState().activeProfile===e)c.setState({activeProfile:null});await this.refreshData()}else alert(`Failed to delete profile: ${t.error||"Unknown error"}`)}catch(t){alert(`Error deleting profile: ${t.message}`)}}async toggleServerInProfile(e,t,a){let n=c.getState().config.profiles?.[e];if(!n)return;let r=[...n.servers||[]];if(a){if(!r.includes(t))r.push(t)}else if(r=r.filter((s)=>s!==t),r.length===0){alert("A profile must contain at least one server. To remove the profile, delete it in the Profiles tab.");return}try{let s=await h.upsertProfile(e,r,n.description,n.policy);if(s.ok)await this.refreshData();else alert(`Failed to update profile constellation: ${s.error||"Unknown error"}`)}catch(s){alert(`Error updating profile constellation: ${s.message}`)}}closeModals(){document.querySelectorAll(".modal-backdrop").forEach((e)=>e.classList.remove("active"))}}function f(e){return String(e||"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}var fe=new ve;window.app=fe;window.addEventListener("DOMContentLoaded",()=>fe.init());
+ `}).join("")}async saveProfile(){let e=document.getElementById("modal-prof-name"),t=document.getElementById("modal-prof-desc"),a=e?.value.trim(),n=t?.value.trim();if(!a){alert("Please enter a profile name");return}let o=document.querySelectorAll(".prof-server-checkbox:checked"),r=[];if(o.forEach((f)=>{r.push(f.value)}),r.length===0){alert("Please select at least one server to include in this constellation");return}let s=(f)=>{if(!f)return[];return f.split(",").map((x)=>x.trim()).filter((x)=>x.length>0)},l=document.getElementById("modal-prof-allow"),d=document.getElementById("modal-prof-deny"),g=document.getElementById("modal-prof-hitl"),u=document.getElementById("modal-prof-redact"),m=s(l?.value),v=s(d?.value),c=s(g?.value),y=s(u?.value),b=void 0;if(m.length>0||v.length>0||c.length>0||y.length>0)b={allow:m,deny:v,requireApproval:c,redactKeys:y};try{let f=await h.upsertProfile(a,r,n||void 0,b);if(f.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save profile: ${f.error||"Unknown error"}`)}catch(f){alert(`Error saving profile: ${f.message}`)}}async deleteProfile(e){if(!confirm(`Are you sure you want to delete profile '${e}'?`))return;try{let t=await h.deleteProfile(e);if(t.ok){if(p.getState().activeProfile===e)p.setState({activeProfile:null});await this.refreshData()}else alert(`Failed to delete profile: ${t.error||"Unknown error"}`)}catch(t){alert(`Error deleting profile: ${t.message}`)}}async toggleServerInProfile(e,t,a){let o=p.getState().config.profiles?.[e];if(!o)return;let r=[...o.servers||[]];if(a){if(!r.includes(t))r.push(t)}else if(r=r.filter((s)=>s!==t),r.length===0){alert("A profile must contain at least one server. To remove the profile, delete it in the Profiles tab.");return}try{let s=await h.upsertProfile(e,r,o.description,o.policy);if(s.ok)await this.refreshData();else alert(`Failed to update profile constellation: ${s.error||"Unknown error"}`)}catch(s){alert(`Error updating profile constellation: ${s.message}`)}}closeModals(){document.querySelectorAll(".modal-backdrop").forEach((e)=>e.classList.remove("active"))}}var ie=new ne;window.app=ie;window.addEventListener("DOMContentLoaded",()=>ie.init());
diff --git a/ui/dist/index.html b/ui/dist/index.html
index c894494..969a4e7 100644
--- a/ui/dist/index.html
+++ b/ui/dist/index.html
@@ -822,7 +822,7 @@