#!/usr/bin/env python3
"""PoolClaw MCP Server v2.

Exposes PoolClaw pool tools to Claude Code, OpenAI Agents SDK, and any
MCP-compatible AI framework. Run with:

    poolclaw-mcp                                          # after pip install
    python -m poolclaw_mcp.server                        # from source

Add to Claude Code:

    claude mcp add --transport stdio \\
      --env POOLCLAW_SERVER=https://poolclaw-python.onrender.com \\
      --env POOLCLAW_TOKEN=YOUR_TOKEN \\
      poolclaw -- poolclaw-mcp

USAGE:
Just tell Claude what you want to build. Example: "Create a pool to build an ebook
business with 3 agents." Claude will automatically call create_pool() with sensible
defaults, then connect_pool(), then poll_event() and contribute() in a work loop
until the project is complete.

No need to specify task types, methodology, or workflow details—these are handled
automatically during execution.

Environment variables:
    POOLCLAW_SERVER    PoolClaw API URL (default: https://poolclaw-python.onrender.com)
    POOLCLAW_TOKEN     Agent token for authenticated endpoints (required for most tools)
    POOLCLAW_FRONTEND  Frontend URL returned in tool responses for preview links
                       (default: https://poolclaw-frontend.onrender.com)

CLAUDE CODE PREVIEW:
Every tool that creates or fetches a resource returns a "View on site" URL.
In Claude Code, you can open these URLs in the right-side preview panel to watch
the pool, profile, or marketplace page update live as you work via MCP tools.
"""
from __future__ import annotations

import asyncio
import json
import logging
import os
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

import httpx
import websockets
from mcp.server.fastmcp import FastMCP

log = logging.getLogger("poolclaw_mcp")

def _load_credentials() -> tuple[str, str, str]:
    """Load (token, server_url, frontend_url) — env vars first, then ~/.poolclaw/mcp.json.

    This mirrors the Blender MCP pattern: credentials live in a local file written
    once by poolclaw-mcp-setup, so the MCP works without relying on Claude Code
    correctly passing env vars across sessions.
    """
    token = os.environ.get("POOLCLAW_TOKEN", "")
    server = os.environ.get("POOLCLAW_SERVER", "")
    frontend = os.environ.get("POOLCLAW_FRONTEND", "")

    creds_path = Path.home() / ".poolclaw" / "mcp.json"
    if creds_path.exists() and (not token or not server):
        try:
            data = json.loads(creds_path.read_text(encoding="utf-8"))
            if not token:
                token = data.get("token", "")
            if not server:
                server = data.get("server", "")
            if not frontend:
                frontend = data.get("frontend", "")
        except Exception:
            pass

    server = server or "https://poolclaw-python.onrender.com"
    frontend = frontend or "https://poolclaw-frontend.onrender.com"
    return token, server, frontend


_ENV_TOKEN, SERVER_URL, FRONTEND_URL = _load_credentials()

# Max contribution length — prevent oversized WebSocket payloads
_MAX_CONTENT_LEN = 50_000

mcp = FastMCP("poolclaw", log_level="WARNING")

# ── Session state (one connection per server process) ─────────────────────────
_queue: asyncio.Queue[dict] | None = None
_ws_handle: list[Any] = []   # holds the live websocket object, or empty
_ws_task: asyncio.Task | None = None  # current listener task — cancelled on reconnect
_session: dict[str, str] = {}
_profile_synced = False  # auto-sync runs once per process

# ── Local capability detection ─────────────────────────────────────────────────

# MCP server name → specialty label
_MCP_SPECIALTY: dict[str, str] = {
    # ── Design & Creative ─────────────────────────────────────────────────────
    "blender":              "3D Design",
    "figma":                "UI/UX Design",
    "unity":                "Game Development",
    "godot":                "Game Development",
    "unreal":               "Game Development",
    "maya":                 "3D Design",
    "cinema4d":             "3D Design",
    "storybook":            "Frontend Development",
    "chromatic":            "Frontend Development",
    "canva":                "Visual Design",
    "stable-diffusion":     "AI Image Generation",
    "midjourney":           "AI Image Generation",
    "dall-e":               "AI Image Generation",
    "leonardo":             "AI Image Generation",
    "runway":               "AI Video Generation",
    "eleven-labs":          "AI Voice & Audio",
    "suno":                 "AI Music Generation",
    "assemblyai":           "AI Voice & Audio",
    "deepgram":             "AI Voice & Audio",
    # ── Development Platforms ────────────────────────────────────────────────
    "github":               "Software Development",
    "gitlab":               "Software Development",
    "bitbucket":            "Software Development",
    "azure-devops":         "DevOps & Deployment",
    "sourcegraph":          "Code Search & Navigation",
    "coderabbit":           "Code Review",
    "greptile":             "Code Search & Navigation",
    # ── Web Automation & Scraping ────────────────────────────────────────────
    "puppeteer":            "Web Automation",
    "playwright":           "Web Automation",
    "selenium":             "Web Automation",
    "firecrawl":            "Web Research",
    "tavily":               "Web Research",
    "exa":                  "Web Research",
    "brave-search":         "Web Research",
    "serp-api":             "Web Research",
    "apify":                "Web Automation",
    "bright-data":          "Web Scraping",
    "oxylabs":              "Web Scraping",
    "fetch":                "Web Development",
    "you":                  "Web Research",
    "perplexity":           "AI-powered Research",
    "phind":                "Developer Research",
    # ── Databases ───────────────────────────────────────────────────────────
    "postgres":             "Database Engineering",
    "supabase":             "Database Engineering",
    "sqlite":               "Database Engineering",
    "mysql":                "Database Engineering",
    "mongodb":              "Database Engineering",
    "redis":                "Backend Engineering",
    "neo4j":                "Graph Database Engineering",
    "elasticsearch":        "Search Engineering",
    "qdrant":               "Vector Database & AI Search",
    "pinecone":             "Vector Database & AI Search",
    "weaviate":             "Vector Database & AI Search",
    "chroma":               "Vector Database & AI Search",
    "milvus":               "Vector Database & AI Search",
    "planetscale":          "Database Engineering",
    "neon":                 "Database Engineering",
    "turso":                "Database Engineering",
    "dynamodb":             "Cloud Database Engineering",
    "firestore":            "Cloud Database Engineering",
    "influxdb":             "Time-series Database",
    "cockroachdb":          "Database Engineering",
    # ── Infrastructure & DevOps ──────────────────────────────────────────────
    "vercel":               "DevOps & Deployment",
    "netlify":              "DevOps & Deployment",
    "aws":                  "Cloud Infrastructure",
    "gcp":                  "Cloud Infrastructure",
    "azure":                "Cloud Infrastructure",
    "cloudflare":           "Cloud Infrastructure",
    "docker":               "DevOps & Deployment",
    "kubernetes":           "DevOps & Deployment",
    "terraform":            "Infrastructure as Code",
    "ansible":              "Infrastructure as Code",
    "jenkins":              "CI/CD Pipelines",
    "github-actions":       "CI/CD Pipelines",
    "circleci":             "CI/CD Pipelines",
    "fly":                  "DevOps & Deployment",
    "railway":              "DevOps & Deployment",
    "render":               "DevOps & Deployment",
    "heroku":               "DevOps & Deployment",
    "digitalocean":         "Cloud Infrastructure",
    "dagger":               "CI/CD Pipelines",
    "firebase":             "Cloud Infrastructure",
    # ── Observability & Monitoring ───────────────────────────────────────────
    "datadog":              "Observability & Monitoring",
    "pagerduty":            "Incident Management",
    "sentry":               "Error Tracking",
    "grafana":              "Observability & Monitoring",
    "prometheus":           "Observability & Monitoring",
    "newrelic":             "Observability & Monitoring",
    "bugsnag":              "Error Tracking",
    "logrocket":            "Frontend Monitoring",
    # ── Project & Product Management ─────────────────────────────────────────
    "linear":               "Product Management",
    "jira":                 "Project Management",
    "asana":                "Project Management",
    "clickup":              "Project Management",
    "monday":               "Project Management",
    "notion":               "Productivity & Knowledge",
    "trello":               "Project Management",
    "shortcut":             "Product Management",
    "height":               "Project Management",
    "smartsheet":           "Project Management",
    "basecamp":             "Project Management",
    "miro":                 "Visual Collaboration",
    "confluence":           "Knowledge Management",
    "obsidian":             "Knowledge Management",
    "roam":                 "Knowledge Management",
    "logseq":               "Knowledge Management",
    # ── Communication ───────────────────────────────────────────────────────
    "slack":                "Team Communication",
    "discord":              "Community Management",
    "telegram":             "Bot Development",
    "microsoft-graph":      "Microsoft 365 Automation",
    "teams":                "Team Communication",
    "zoom":                 "Video Communication",
    "gmail":                "Email Automation",
    "google-calendar":      "Calendar Automation",
    "google-drive":         "Cloud Storage",
    "onedrive":             "Cloud Storage",
    "outlook":              "Email Automation",
    "sendgrid":             "Email Marketing",
    "mailchimp":            "Email Marketing",
    "brevo":                "Email Marketing",
    "klaviyo":              "Email Marketing",
    "activecampaign":       "Marketing Automation",
    "twilio":               "SMS & Voice Communication",
    # ── CRM & Sales ─────────────────────────────────────────────────────────
    "salesforce":           "CRM & Sales",
    "hubspot":              "CRM & Sales",
    "pipedrive":            "CRM & Sales",
    "intercom":             "Customer Support",
    "zendesk":              "Customer Support",
    "freshdesk":            "Customer Support",
    # ── E-commerce & Payments ───────────────────────────────────────────────
    "shopify":              "E-commerce",
    "woocommerce":          "E-commerce",
    "stripe":               "Payment Integration",
    "paypal":               "Payment Integration",
    "braintree":            "Payment Integration",
    "adyen":                "Payment Integration",
    "plaid":                "Fintech Integration",
    "quickbooks":           "Accounting Automation",
    "xero":                 "Accounting Automation",
    # ── CMS & Content ───────────────────────────────────────────────────────
    "wordpress":            "CMS Development",
    "contentful":           "Headless CMS",
    "sanity":               "Headless CMS",
    "strapi":               "Headless CMS",
    "ghost":                "Content Publishing",
    "webflow":              "No-code Web Design",
    "airtable":             "Data Management",
    # ── Analytics & Marketing ────────────────────────────────────────────────
    "google-analytics":     "Web Analytics",
    "mixpanel":             "Product Analytics",
    "amplitude":            "Product Analytics",
    "posthog":              "Product Analytics",
    "segment":              "Data Integration",
    "rudderstack":          "Data Integration",
    "hotjar":               "UX Analytics",
    "google-ads":           "Digital Marketing",
    "meta-ads":             "Digital Marketing",
    # ── Data & Automation ────────────────────────────────────────────────────
    "zapier":               "Workflow Automation",
    "make":                 "Workflow Automation",
    "n8n":                  "Workflow Automation",
    "activepieces":         "Workflow Automation",
    "temporal":             "Workflow Orchestration",
    "airflow":              "Data Pipeline Orchestration",
    "prefect":              "Data Pipeline Orchestration",
    "dagster":              "Data Pipeline Orchestration",
    "dbt":                  "Data Transformation",
    "airbyte":              "Data Integration",
    "fivetran":             "Data Integration",
    # ── AI & Machine Learning ────────────────────────────────────────────────
    "openai":               "AI Development",
    "anthropic":            "AI Development",
    "huggingface":          "AI Development",
    "langchain":            "AI Agent Development",
    "llamaindex":           "AI Agent Development",
    "replicate":            "AI Model Deployment",
    "together-ai":          "AI Model Inference",
    "groq":                 "AI Model Inference",
    "ollama":               "Local AI Development",
    "mistral":              "AI Development",
    "cohere":               "AI Development",
    "dspy":                 "AI Agent Development",
    "autogen":              "AI Agent Development",
    "crewai":               "AI Agent Development",
    "mlflow":               "ML Experiment Tracking",
    "wandb":                "ML Experiment Tracking",
    "modal":                "AI Model Deployment",
    "memory":               "AI Memory Systems",
    "sequential-thinking":  "Strategic Analysis",
    # ── Blockchain & Web3 ────────────────────────────────────────────────────
    "alchemy":              "Blockchain Development",
    "moralis":              "Blockchain Development",
    "etherscan":            "Blockchain Analytics",
    "solana":               "Blockchain Development",
    "near":                 "Blockchain Development",
    "opensea":              "NFT & Web3",
    "coinbase":             "Crypto Exchange Integration",
    "binance":              "Crypto Exchange Integration",
    # ── System & Files ───────────────────────────────────────────────────────
    "filesystem":           "System Automation",
    "docker":               "DevOps & Deployment",
    "amazon-s3":            "Cloud Storage",
    "google-cloud-storage": "Cloud Storage",
    # ── Maps & Location ──────────────────────────────────────────────────────
    "google-maps":          "Geolocation & Mapping",
    "mapbox":               "Geolocation & Mapping",
    # ── OpenClaw-specific ────────────────────────────────────────────────────
    "poolclaw":             "Multi-Agent Collaboration",
    "openclaw":             "Multi-Agent Collaboration",
}

# Claude plugin id → specialty label (Harbor marketplace + community plugins)
_PLUGIN_SPECIALTY: dict[str, str] = {
    # Communication
    "discord":              "Discord Bot Development",
    "telegram":             "Telegram Bot Development",
    "slack":                "Slack Integration",
    "teams":                "Microsoft Teams Integration",
    "zoom":                 "Video Communication",
    "whatsapp":             "WhatsApp Automation",
    # Development
    "github":               "Software Development",
    "gitlab":               "Software Development",
    "replit":               "Cloud IDE & Collaboration",
    "codesandbox":          "Cloud IDE & Collaboration",
    "stackblitz":           "Cloud IDE & Collaboration",
    # Productivity
    "notion":               "Notion Automation",
    "obsidian":             "Knowledge Management",
    "google-drive":         "Cloud Storage",
    "google-calendar":      "Calendar Automation",
    "google-docs":          "Document Automation",
    "onedrive":             "Cloud Storage",
    "dropbox":              "Cloud Storage",
    "gmail":                "Email Automation",
    "outlook":              "Email Automation",
    "trello":               "Project Management",
    "asana":                "Project Management",
    "linear":               "Product Management",
    "clickup":              "Project Management",
    "todoist":              "Task Management",
    "airtable":             "Data Management",
    "smartsheet":           "Project Management",
    "miro":                 "Visual Collaboration",
    "loom":                 "Video Communication",
    # CRM & Sales
    "hubspot":              "CRM & Sales",
    "salesforce":           "CRM & Sales",
    "pipedrive":            "CRM & Sales",
    "intercom":             "Customer Support",
    "zendesk":              "Customer Support",
    # Commerce & Finance
    "stripe":               "Payment Integration",
    "shopify":              "E-commerce",
    "woocommerce":          "E-commerce",
    "quickbooks":           "Accounting Automation",
    "paypal":               "Payment Integration",
    # Marketing
    "mailchimp":            "Email Marketing",
    "sendgrid":             "Email Marketing",
    "brevo":                "Email Marketing",
    "klaviyo":              "Email Marketing",
    "activecampaign":       "Marketing Automation",
    "google-ads":           "Digital Marketing",
    "meta-ads":             "Digital Marketing",
    # Data & Automation
    "zapier":               "Workflow Automation",
    "make":                 "Workflow Automation",
    "n8n":                  "Workflow Automation",
    # Content & CMS
    "wordpress":            "CMS Development",
    "contentful":           "Headless CMS",
    "webflow":              "No-code Web Design",
    "canva":                "Visual Design",
    # Analytics
    "google-analytics":     "Web Analytics",
    "mixpanel":             "Product Analytics",
    "posthog":              "Product Analytics",
    "segment":              "Data Integration",
    # Misc
    "fakechat":             "Chat Simulation",
    "apple-shortcuts":      "System Automation",
    "raycast":              "Productivity Automation",
}

# Claude Code skill id prefix → specialty label (all official + community skills)
_SKILL_SPECIALTY: dict[str, str] = {
    # Design & Frontend
    "frontend-design":      "Frontend Development",
    "figma":                "UI/UX Design",
    # Web Research
    "firecrawl":            "Web Research & Scraping",
    # DevOps
    "vercel":               "DevOps & Deployment",
    # Payments
    "stripe":               "Payment Integration",
    # AI Development
    "claude-api":           "AI Development",
    "anthropic-skills":     "AI Development",
    "agent-sdk-dev":        "AI Agent Development",
    # Code Quality
    "pr-review-toolkit":    "Code Review",
    "feature-dev":          "Software Development",
    "code-simplifier":      "Code Quality",
    "simplify":             "Code Quality",
    # Office & Documents
    "ms-office-suite":      "Document Automation",
    # Productivity
    "update-config":        "Developer Tooling",
    "keybindings-help":     "Developer Tooling",
    "loop":                 "Workflow Automation",
    "anthropic-skills:schedule": "Task Scheduling",
    # Framework-specific
    "nextjs":               "Frontend Development",
    "react":                "Frontend Development",
    "vue":                  "Frontend Development",
    "svelte":               "Frontend Development",
    "tailwind":             "Frontend Development",
    "django":               "Backend Development",
    "fastapi":              "Backend Development",
    "rails":                "Backend Development",
    "laravel":              "Backend Development",
}


# ── OpenClaw (ClawHub) skill detection ────────────────────────────────────────
# Source: https://github.com/VoltAgent/awesome-openclaw-skills (5,400+ curated)
# OpenClaw installs skills as directories under ~/.openclaw/skills/<skill-name>/
# Each skill has a SKILL.md file with YAML frontmatter.

# Skill name/prefix → specialty label (covers all 30 categories from ClawHub)
_OPENCLAW_SKILL_SPECIALTY: dict[str, str] = {
    # === Custom / PaperBot ===
    "paperbot":                         "Telegram Bot Development",

    # === Git & GitHub ===
    "github":                           "Software Development",
    "git":                              "Software Development",
    "gitlab":                           "Software Development",
    "gitflow":                          "Software Development",
    "github-manager":                   "Software Development",
    "pr-reviewer":                      "Code Review",
    "gh":                               "GitHub Automation",
    "gh-action-gen":                    "CI/CD Pipelines",
    "gh-extract":                       "GitHub Automation",
    "git-changelog":                    "GitHub Automation",
    "git-changelog-gen":                "GitHub Automation",
    "git-crypt-backup":                 "GitHub Automation",
    "git-essentials":                   "GitHub Automation",
    "git-helper":                       "GitHub Automation",
    "git-pushing":                      "GitHub Automation",
    "git-sentinel":                     "GitHub Automation",
    "git-summary":                      "GitHub Automation",
    "git-workflows":                    "CI/CD Pipelines",
    "glab-cli":                         "GitHub Automation",
    "bitbucket-automation":             "GitHub Automation",
    "auto-pr-merger":                   "CI/CD Pipelines",
    "commit-analyzer":                  "Code Review",
    "conventional-commits":             "Software Development",
    "pr-risk-analyzer":                 "Code Review",
    "repo-pr-triage":                   "Code Review",
    "super-github":                     "GitHub Automation",
    "sovereign-changelog-maker":        "GitHub Automation",
    "sovereign-commit-craft":           "GitHub Automation",
    "sovereign-git-commit-analyzer":    "GitHub Automation",
    "cross-model-review":               "Code Review",
    "find-code-tasks":                  "Software Development",
    "task-development-workflow":        "Software Development",
    "task-review-workflow":             "Code Review",
    "neo-github-readme-generator":      "GitHub Automation",
    "deepwiki":                         "Knowledge Management",
    "release-tracker":                  "Software Development",
    "deploy-agent":                     "DevOps & Deployment",
    "jenkins":                          "CI/CD Pipelines",
    "azure-devops":                     "CI/CD Pipelines",
    "arc-skill-gitops":                 "CI/CD Pipelines",
    "arc-security-audit":               "Cybersecurity",
    "skill-security-reviewer":          "Cybersecurity",
    "upstream-recon":                   "Software Development",
    "fork-and-skill-scanner-ultimate":  "Software Development",
    "forkzoo":                          "Software Development",

    # === Coding Agents & IDEs ===
    "code-runner":                      "Backend Development",
    "code-review":                      "Code Review",
    "cursor":                           "Developer Tooling",
    "vscode":                           "Developer Tooling",
    "replit":                           "Developer Tooling",
    "claude-code":                      "AI Development",
    "claude-code-skill":                "AI Development",
    "gemini":                           "AI Development",
    "copilot":                          "AI Development",
    "aider":                            "AI Development",
    "continue":                         "AI Development",
    "codeium":                          "AI Development",
    "open-interpreter":                 "AI Development",
    "devin":                            "AI Agent Development",
    "swe-agent":                        "AI Agent Development",
    "product-manager-skills":           "Product Management",
    "mcporter":                         "Developer Tooling",
    "aetherlang-claude-code":           "AI Development",
    "agent-audit":                      "Developer Tooling",
    "agent-audit-trail":                "Developer Tooling",
    "agent-cost-monitor":               "Developer Tooling",
    "agent-guardrails":                 "AI Agent Development",
    "agent-os":                         "AI Agent Development",
    "agent-safety":                     "AI Agent Development",
    "agent-synthesizer":                "AI Agent Development",
    "agentbench":                       "AI Development",
    "agentcli-go":                      "Developer Tooling",
    "agentdojo":                        "AI Development",
    "agentgate-security":               "Cybersecurity",
    "agentlens":                        "Developer Tooling",
    "agentok-skill":                    "Developer Tooling",
    "agentshield-audit":                "Cybersecurity",
    "agentskills-io":                   "Developer Tooling",
    "ai-agent-tools":                   "AI Agent Development",
    "ai-shield-audit":                  "Cybersecurity",
    "ai-usage":                         "Developer Tooling",
    "ai-workforce":                     "AI Agent Development",
    "audit-code":                       "Code Review",
    "agents-skill-tdd-helper":          "Software Development",
    "baml-codegen":                     "AI Development",
    "3d-model-generation":              "AI Image Generation",
    "3d-cog":                           "AI Image Generation",
    "agent-context":                    "AI Agent Development",
    "agent-config":                     "Developer Tooling",
    "agent-council":                    "AI Agent Development",
    "agent-failure-registry":           "Developer Tooling",
    "agent-puzzles":                    "Developer Tooling",
    "agent-republic":                   "AI Agent Development",
    "agi-terminal-helper":              "Developer Tooling",
    "ai-prompt-gen":                    "AI Development",
    "ai-ppt-generator":                 "Productivity Automation",
    "ai-presentation-maker":            "Productivity Automation",
    "agent-nestjs-skills":              "Backend Development",
    "academic-research":                "Research & Analysis",
    "academic-research-hub":            "Research & Analysis",
    "agent-media":                      "Media Automation",
    "ai-notes-video":                   "Document Processing",
    "ai-ugc":                           "Content Creation",

    # === Web & Frontend Development ===
    "nextjs":                           "Frontend Development",
    "react":                            "Frontend Development",
    "tailwind":                         "Frontend Development",
    "figma":                            "UI/UX Design",
    "webflow":                          "No-code Web Design",
    "vercel":                           "DevOps & Deployment",
    "netlify":                          "DevOps & Deployment",
    "ahrefs":                           "Web Research",
    "ahrefs-connection":                "Web Research",
    "ahrefs-mcp":                       "Web Research",
    "anima-design-agent":               "UI/UX Design",
    "ant-design-skill":                 "Frontend Development",
    "api-dev":                          "Backend Development",
    "app-builder":                      "Frontend Development",
    "artifacts-builder":                "Frontend Development",
    "awwwards-design":                  "UI/UX Design",
    "axe-devtools":                     "Frontend Development",
    "backlink-analyzer":                "Web Research",
    "seo-content-engine":               "Marketing Automation",
    "seo-content-factory":              "Marketing Automation",
    "seo-content-writer":               "Marketing Automation",
    "seo-ranker":                       "Marketing Automation",
    "serp-analysis":                    "Web Research",
    "meta-tags-optimizer":              "Marketing Automation",
    "agent-analytics":                  "Product Analytics",
    "agent-dashboard":                  "Frontend Development",
    "agent-dispatch":                   "AI Agent Development",
    "agent-hq":                         "Developer Tooling",
    "agent-swarm":                      "AI Agent Development",
    "agent-spawner":                    "AI Agent Development",
    "agentscale":                       "AI Agent Development",
    "agentpay":                         "Payment Integration",
    "agentpin":                         "Developer Tooling",
    "agentspend":                       "Payment Integration",
    "airbnb-search":                    "Travel Automation",
    "app-store-optimization":           "Marketing Automation",
    "posthog":                          "Product Analytics",
    "posthog-query":                    "Product Analytics",

    # === Browser & Automation ===
    "browser":                          "Web Automation",
    "agent-browser":                    "Web Automation",
    "puppeteer":                        "Web Automation",
    "playwright":                       "Web Automation",
    "selenium":                         "Web Automation",
    "screenshot":                       "Web Automation",
    "web-scraper":                      "Web Research",
    "http-request":                     "API Integration",
    "cron-scheduler":                   "Workflow Automation",
    "browser-use":                      "Web Automation",
    "cdp-browser":                      "Web Automation",
    "camoufox":                         "Web Automation",
    "anycrawl":                         "Web Research",
    "2captcha":                         "Web Automation",
    "activecampaign":                   "Marketing Automation",
    "airtable-automation":              "Productivity Automation",
    "airtable-participants":            "Productivity Automation",
    "amazon-product-search-api-skill":  "E-Commerce Automation",
    "amazon-reviews-api-skill":         "E-Commerce Automation",
    "amazon-shopper":                   "E-Commerce Automation",
    "android-adb":                      "Mobile Development",
    "automation-workflows":             "Workflow Automation",
    "bamboohr-automation":              "HR Automation",
    "basecamp-automation":              "Project Management",
    "box-automation":                   "Document Processing",
    "browser-ladder":                   "Web Automation",
    "browsh":                           "Web Automation",
    "cal-com-automation":               "Calendar Automation",
    "coda-io":                          "Productivity Automation",
    "coingecko":                        "Crypto Exchange Integration",
    "daily-news":                       "Web Research",
    "db-readonly":                      "Database Engineering",
    "dune-analytics-api":               "Data Analysis",
    "exa-tool":                         "Web Research",
    "erpnext-frappe":                   "Business Automation",
    "accessibility-toolkit":            "Frontend Development",
    "apify-lead-generation":            "Lead Generation",
    "auto-updater":                     "System Automation",
    "bookmark-intelligence":            "Knowledge Management",

    # === DevOps & Cloud ===
    "docker":                           "DevOps & Deployment",
    "kubernetes":                       "DevOps & Deployment",
    "terraform":                        "Infrastructure as Code",
    "aws":                              "Cloud Infrastructure",
    "gcp":                              "Cloud Infrastructure",
    "azure":                            "Cloud Infrastructure",
    "cloudflare":                       "Cloud Infrastructure",
    "github-actions":                   "CI/CD Pipelines",
    "fly":                              "DevOps & Deployment",
    "railway":                          "DevOps & Deployment",
    "railway-deploy":                   "DevOps & Deployment",
    "self-hosted":                      "Self-hosted Infrastructure",
    "ansible-skill":                    "Infrastructure as Code",
    "aws-ecs-monitor":                  "Cloud Infrastructure",
    "aws-infra":                        "Cloud Infrastructure",
    "aws-security-scanner":             "Cybersecurity",
    "aws-solution-architect":           "Cloud Infrastructure",
    "awscli":                           "Cloud Infrastructure",
    "azd-deployment":                   "Cloud Infrastructure",
    "azure-cli":                        "Cloud Infrastructure",
    "azure-infra":                      "Cloud Infrastructure",
    "azure-ai-agents-py":               "AI Agent Development",
    "azure-image-gen":                  "AI Image Generation",
    "azure-keyvault-py":                "Security & Credentials",
    "cloudflare-guard":                 "Security & Credentials",
    "cloudflare-image-gen":             "AI Image Generation",
    "docker-skill":                     "DevOps & Deployment",
    "elasticsearch-skill":              "Database Engineering",
    "hcloud":                           "Cloud Infrastructure",
    "homelab-cluster":                  "Self-hosted Infrastructure",
    "homeserver":                       "Self-hosted Infrastructure",
    "hostinger-vps-optimizer":          "Cloud Infrastructure",
    "k8s-memory-tune":                  "DevOps & Deployment",
    "laravel-forge":                    "DevOps & Deployment",
    "librenms":                         "DevOps & Deployment",
    "mcp-server-discovery":             "Developer Tooling",
    "mcp-ssh-manager":                  "DevOps & Deployment",
    "neo-docker-to-k8s-manifests":      "Infrastructure as Code",
    "neo-tf-module-generator":          "Infrastructure as Code",
    "openbot-esxi":                     "DevOps & Deployment",
    "opnsense-admin":                   "Cybersecurity",
    "platform-healthcheck":             "DevOps & Deployment",
    "runpod":                           "Cloud Infrastructure",
    "secrets-management":               "Security & Credentials",
    "server-health-agent":              "DevOps & Deployment",
    "service-watchdog":                 "DevOps & Deployment",
    "sovereign-aws-cost-optimizer":     "Cloud Infrastructure",
    "ssh-op":                           "DevOps & Deployment",
    "system-watchdog":                  "DevOps & Deployment",
    "tf-plan-review":                   "Infrastructure as Code",
    "vault":                            "Security & Credentials",
    "vps-health-auditor":               "Cloud Infrastructure",
    "agent-framework-azure-ai-py":      "AI Agent Development",
    "agentic-devops":                   "DevOps & Deployment",
    "grafana-lens":                     "DevOps & Deployment",
    "grafana-plugin":                   "DevOps & Deployment",
    "rollbar":                          "DevOps & Deployment",
    "log-dive":                         "DevOps & Deployment",

    # === AI & LLMs ===
    "openai":                           "AI Development",
    "anthropic":                        "AI Development",
    "langchain":                        "AI Agent Development",
    "llamaindex":                       "AI Agent Development",
    "ollama":                           "Local AI Development",
    "groq":                             "AI Model Inference",
    "together":                         "AI Model Inference",
    "mistral":                          "AI Development",
    "cohere":                           "AI Development",
    "huggingface":                      "AI Development",
    "autogen":                          "AI Agent Development",
    "crewai":                           "AI Agent Development",
    "model-switcher":                   "AI Development",
    "ollama-model-tuner":               "Local AI Development",
    "llmcouncil-router":                "AI Development",
    "llmfit":                           "AI Development",
    "multi-agent-collab":               "AI Agent Development",
    "agent-orchestrator":               "AI Agent Development",
    "agent-memory":                     "AI Agent Development",
    "agent-registry":                   "AI Agent Development",
    "adversarial-prompting":            "AI Development",
    "ai-conversation-summary":          "AI Development",
    "aisa-llm-router-skill":            "AI Development",
    "arya-model-router":                "AI Development",
    "asia-llm-router-skills":           "AI Development",
    "astrai-inference-router":          "AI Development",
    "context-gatekeeper":               "AI Development",
    "decompose-mcp":                    "AI Agent Development",
    "deepseek-reasoner-lite-agent":     "AI Development",
    "deepseek-v3-lite-agent":           "AI Development",
    "evoagentx":                        "AI Agent Development",
    "groq-2":                           "AI Model Inference",
    "metacognition":                    "AI Development",
    "model-guard":                      "AI Development",
    "modelready":                       "AI Development",
    "moa":                              "AI Agent Development",
    "agent-orchestration-multi-agent-optimize": "AI Agent Development",
    "agent-ethos":                      "AI Development",
    "agent-docs":                       "AI Development",
    "universal-skills-manager":         "AI Agent Development",
    "safety-checks":                    "AI Development",
    "screen-vision":                    "AI Development",
    "smart-context":                    "AI Development",
    "wolfram-alpha":                    "Research & Analysis",
    "x-ai":                             "AI Development",
    "which-llm":                        "AI Development",
    "meeting-summarizer":               "Productivity Automation",
    "meeting-autopilot":                "Productivity Automation",
    "audio-processing":                 "Speech & Audio",
    "conversational-ai-assistant":      "AI Development",
    "ai-humanizer":                     "AI Development",
    "ai-writing-humanizer":             "AI Development",

    # === Communication ===
    "slack":                            "Team Communication",
    "discord":                          "Community Management",
    "telegram":                         "Bot Development",
    "whatsapp":                         "Bot Development",
    "signal":                           "Bot Development",
    "imessage":                         "Bot Development",
    "teams":                            "Team Communication",
    "matrix":                           "Bot Development",
    "gmail":                            "Email Automation",
    "outlook":                          "Email Automation",
    "sendgrid":                         "Email Marketing",
    "twilio":                           "SMS & Voice Communication",
    "clawd-talk":                       "SMS & Voice Communication",
    "clawr-ing":                        "AI Voice Integration",
    "agent-mail":                       "Email Automation",
    "agent-mail-cli":                   "Email Automation",
    "agentmail":                        "Email Automation",
    "clawemail":                        "Email Automation",
    "clawemail-admin":                  "Email Automation",
    "disposable-email-for-agents":      "Email Automation",
    "email-autoreply":                  "Email Automation",
    "email-manager-ai":                 "Email Automation",
    "email-resend":                     "Email Marketing",
    "gmail-last5":                      "Email Automation",
    "gmail-oauth":                      "Email Automation",
    "gmail-tool":                       "Email Automation",
    "gmail-secretary":                  "Email Automation",
    "greek-email-processor":            "Email Automation",
    "intercom-conversations":           "Team Communication",
    "lnemail":                          "Email Automation",
    "localsend":                        "Team Communication",
    "microsoft365":                     "Team Communication",
    "outbound-call":                    "SMS & Voice Communication",
    "rocketchat":                       "Team Communication",
    "telegrambot":                      "Bot Development",
    "telnyx-freemium-upgrade":          "SMS & Voice Communication",
    "voice-email":                      "Email Automation",
    "zulip":                            "Team Communication",
    "custom-smtp-sender":               "Email Automation",
    "apple-mail-search-safe":           "Email Automation",
    "agent-social":                     "Social Media Automation",
    "beeper":                           "Bot Development",
    "clawchat-p2p":                     "Bot Development",
    "groupme-cli":                      "Team Communication",
    "communication-skill":              "Team Communication",
    "pidgesms":                         "SMS & Voice Communication",
    "sixel-email":                      "Email Automation",
    "tsend":                            "SMS & Voice Communication",
    "teltel-send-sms-text-message":     "SMS & Voice Communication",
    "freemobile-sms":                   "SMS & Voice Communication",

    # === Search & Research ===
    "web-search":                       "Web Research",
    "web-search-pro":                   "Web Research",
    "serpapi":                          "Web Research",
    "serpapi-mcp":                      "Web Research",
    "firecrawl":                        "Web Research & Scraping",
    "tavily":                           "Web Research",
    "exa":                              "Web Research",
    "brave":                            "Web Research",
    "perplexity":                       "AI-powered Research",
    "academic-search":                  "Research & Analysis",
    "arxiv":                            "Research & Analysis",
    "pubmed":                           "Research & Analysis",
    "academic-deep-research":           "Research & Analysis",
    "academic-writer":                  "Research & Analysis",
    "academic-writing":                 "Research & Analysis",
    "agent-deep-research":              "Research & Analysis",
    "agent-brain":                      "Research & Analysis",
    "agentarxiv":                       "Research & Analysis",
    "agentic-paper-digest":             "Research & Analysis",
    "arxiv-batch-reporter":             "Research & Analysis",
    "arxiv-cli-tools":                  "Research & Analysis",
    "arxiv-paper-processor":            "Research & Analysis",
    "arxiv-paper-reviews":              "Research & Analysis",
    "arxiv-summarizer-orchestrator":    "Research & Analysis",
    "arxiv-watcher":                    "Research & Analysis",
    "baidu-scholar-search":             "Web Research",
    "baidu-search":                     "Web Research",
    "bing-search":                      "Web Research",
    "books-for-agents":                 "Research & Analysis",
    "caesar-research":                  "Research & Analysis",
    "content-research":                 "Research & Analysis",
    "cortex-ai":                        "AI-powered Research",
    "dify-kb-search":                   "Knowledge Management",
    "exa-web-search-free":              "Web Research",
    "finance-search-agent":             "Research & Analysis",
    "google-trends":                    "Web Research",
    "hybrid-deep-search":               "Web Research",
    "ikuzo":                            "Research & Analysis",
    "japan-news-mcp":                   "Web Research",
    "legal-cog":                        "Research & Analysis",
    "librarian":                        "Knowledge Management",
    "literature-manager":               "Research & Analysis",
    "media-writing":                    "Content Creation",
    "med-info":                         "Research & Analysis",
    "medical-clinicaltrials":           "Research & Analysis",
    "meyhem-researcher":                "Research & Analysis",
    "meyhem-search":                    "Web Research",
    "nyne-search":                      "Web Research",
    "openclaw-free-web-search":         "Web Research",
    "perplexity-deep-search":           "AI-powered Research",
    "pubmed-edirect":                   "Research & Analysis",
    "pubmed2blog":                      "Research & Analysis",
    "ragflow":                          "AI Agent Development",
    "ragie-rag":                        "AI Agent Development",
    "research-paper-kb":                "Research & Analysis",
    "research-report":                  "Research & Analysis",
    "rss-skill":                        "Web Research",
    "search-cluster":                   "Web Research",
    "searchbar":                        "Web Research",
    "session-history":                  "Developer Tooling",
    "skill-seo":                        "Marketing Automation",
    "social-intelligence":              "Marketing Automation",
    "super-research":                   "Research & Analysis",
    "twitter-api-alternative":          "Social Media Automation",
    "twitter-x-api":                    "Social Media Automation",
    "twitterapi-io":                    "Social Media Automation",
    "wiki-retriever":                   "Research & Analysis",
    "wikipedia-oc":                     "Research & Analysis",
    "x-research-but-cheaper":          "Web Research",
    "yacy":                             "Web Research",
    "aminer-open-academic":             "Research & Analysis",
    "law-search":                       "Research & Analysis",
    "swiftscholar-skill":               "Research & Analysis",

    # === Marketing & Sales ===
    "tweetclaw":                        "Social Media Automation",
    "x-twitter":                        "Social Media Automation",
    "twitter":                          "Social Media Automation",
    "linkedapi":                        "Professional Network Automation",
    "linkedin":                         "Professional Network Automation",
    "mailchimp":                        "Email Marketing",
    "hubspot":                          "CRM & Sales",
    "salesforce":                       "CRM & Sales",
    "lead-gen":                         "Lead Generation",
    "ads":                              "Digital Marketing",
    "ad-ready":                         "Digital Marketing",
    "ad-ready-pro":                     "Digital Marketing",
    "affiliate-master":                 "Marketing Automation",
    "affiliatematic":                   "Marketing Automation",
    "apollo":                           "Lead Generation",
    "attio-enhanced":                   "CRM & Sales",
    "attio-crm":                        "CRM & Sales",
    "attribution-engine":               "Product Analytics",
    "b2c-marketing":                    "Marketing Automation",
    "bluesky":                          "Social Media Automation",
    "brand-cog":                        "Marketing Automation",
    "brand-guidelines":                 "Marketing Automation",
    "brand-voice-profile":              "Marketing Automation",
    "brevo":                            "Email Marketing",
    "blog-writer":                      "Content Creation",
    "buzz-bd":                          "Lead Generation",
    "campaign-orchestrator":            "Marketing Automation",
    "cold-email":                       "Email Marketing",
    "cold-outreach":                    "Lead Generation",
    "content-creator":                  "Content Creation",
    "content-generation":               "Content Creation",
    "content-repurpose-pro":            "Marketing Automation",
    "dellight-cmo-content-marketing":   "Marketing Automation",
    "dellight-content-marketing":       "Marketing Automation",
    "email-marketing-2":                "Email Marketing",
    "foxreach":                         "Lead Generation",
    "ghost-cms":                        "Content Creation",
    "go-to-market":                     "Marketing Automation",
    "instagram-search":                 "Social Media Automation",
    "kit-email-operator":               "Email Marketing",
    "lead-extractor":                   "Lead Generation",
    "lead-generation":                  "Lead Generation",
    "lead-magnets":                     "Lead Generation",
    "lead-researcher":                  "Lead Generation",
    "marketing-strategy-pmm":           "Marketing Automation",
    "meta-ads-report":                  "Digital Marketing",
    "near-agent-skills":                "AI Agent Development",
    "nicholasrae-review-reply":         "Marketing Automation",
    "outlit-mcp":                       "Marketing Automation",
    "performance-reporter":             "Product Analytics",
    "simplified-social-media":          "Social Media Automation",
    "social-media-lead-generation":     "Lead Generation",
    "social-media-manager":             "Social Media Automation",
    "sovereign-brand-voice-writer":     "Content Creation",
    "tiktok-trend-challenger":          "Social Media Automation",
    "tiktok-viral-marketing":           "Social Media Automation",
    "windmill":                         "Workflow Automation",
    "windsor-ai":                       "Marketing Automation",
    "workcrm":                          "CRM & Sales",
    "writing-assistant":                "Content Creation",
    "kiro-x-publisher":                 "Social Media Automation",

    # === Productivity & Tasks ===
    "notion":                           "Productivity & Knowledge",
    "todoist":                          "Task Management",
    "things":                           "Task Management",
    "task-manager":                     "Task Management",
    "project-manager":                  "Project Management",
    "linear":                           "Product Management",
    "jira":                             "Project Management",
    "asana":                            "Project Management",
    "clickup":                          "Project Management",
    "trello":                           "Project Management",
    "file-manager":                     "System Automation",
    "pdf-reader":                       "Document Processing",
    "pdf":                              "Document Processing",
    "email-drafter":                    "Email Automation",
    "calendar":                         "Calendar Automation",
    "cron":                             "Workflow Automation",
    "4to1-planner":                     "Productivity Automation",
    "actual-budget":                    "Productivity Automation",
    "adaptive-reasoning":               "AI Development",
    "adhd-daily-planner":               "Productivity Automation",
    "agent-autopilot":                  "Workflow Automation",
    "agent-chronicle":                  "Productivity Automation",
    "agent-task-manager":               "Task Management",
    "atlassian-mcp":                    "Project Management",
    "autonomous-execution":             "Workflow Automation",
    "autonomous-executor":              "Workflow Automation",
    "brainz-tasks":                     "Task Management",
    "capacities":                       "Knowledge Management",
    "checkmate":                        "Task Management",
    "clickup-mcp":                      "Project Management",
    "clickup-skill":                    "Project Management",
    "context-aware-delegation":         "AI Agent Development",
    "deep-strategy":                    "Productivity Automation",
    "effortlist-ai":                    "Task Management",
    "excel-workflow":                   "Spreadsheet Automation",
    "family-todo-management":           "Task Management",
    "farm-task-manager":                "Task Management",
    "freedcamp-agent-skill":            "Project Management",
    "goal-setting-okrs":                "Productivity Automation",
    "kanboard-skill":                   "Project Management",
    "longrunning-agent":                "Workflow Automation",
    "loopuman-human-tasks":             "Task Management",
    "mcp-workflow":                     "Workflow Automation",
    "multi-agent-orchestration":        "AI Agent Development",
    "natural-language-planner":         "Productivity Automation",
    "neural-memory":                    "AI Agent Development",
    "ops-hygiene":                      "Productivity Automation",
    "personal-plans":                   "Productivity Automation",
    "plans-methodology":                "Productivity Automation",
    "postqued-api":                     "Marketing Automation",
    "ppt-ooxml-tool":                   "Document Processing",
    "qa-testing-bots":                  "Software Development",
    "reprompter":                       "AI Development",
    "semantic-router":                  "AI Development",
    "task-orchestra":                   "Task Management",
    "task-resume":                      "Task Management",
    "teamo-decision":                   "Productivity Automation",
    "timecamp":                         "Productivity Automation",
    "to-do":                            "Task Management",
    "todo-boss":                        "Task Management",
    "todokan":                          "Task Management",
    "tududi":                           "Task Management",
    "vikunja-kanban":                   "Project Management",
    "vikunja-tasks":                    "Task Management",
    "workflow-engine":                  "Workflow Automation",
    "workflow-tools":                   "Workflow Automation",
    "wrike":                            "Project Management",
    "zonein":                           "Productivity Automation",

    # === Image & Video Generation ===
    "image-gen":                        "AI Image Generation",
    "image-lab":                        "AI Image Generation",
    "stable-diffusion":                 "AI Image Generation",
    "midjourney":                       "AI Image Generation",
    "dall-e":                           "AI Image Generation",
    "runway":                           "AI Video Generation",
    "fal":                              "AI Image Generation",
    "replicate":                        "AI Model Deployment",
    "fal-ai":                           "AI Image Generation",
    "fal-text-to-image":                "AI Image Generation",
    "ai-avatar-generation":             "AI Image Generation",
    "ai-headshot-generation":           "AI Image Generation",
    "ai-video-gen":                     "AI Video Generation",
    "age-transformation":               "AI Image Generation",
    "album-cover-generation":           "AI Image Generation",
    "algorithmic-art":                  "AI Image Generation",
    "ascii-art-generator":              "AI Image Generation",
    "beauty-generation-api":            "AI Image Generation",
    "best-image-generation":            "AI Image Generation",
    "canva-connect":                    "AI Image Generation",
    "captions":                         "AI Video Generation",
    "chart-image":                      "Data Analytics",
    "chart-splat":                      "Data Analytics",
    "cheapest-image-generation":        "AI Image Generation",
    "color-palette":                    "UI/UX Design",
    "coloring-page":                    "AI Image Generation",
    "comfyui":                          "AI Image Generation",
    "comfyui-imagegen":                 "AI Image Generation",
    "data-viz":                         "Data Analytics",
    "depth-map-generation":             "AI Image Generation",
    "eachlabs-face-swap":               "AI Image Generation",
    "eachlabs-fashion-ai":              "AI Image Generation",
    "eachlabs-image-edit":              "AI Image Generation",
    "eachlabs-image-generation":        "AI Image Generation",
    "eachlabs-video-edit":              "AI Video Generation",
    "eachlabs-video-generation":        "AI Video Generation",
    "excalidraw-flowchart":             "Productivity Automation",
    "ffmpeg-video-editor":              "Media Automation",
    "generate-news-article":            "Content Creation",
    "grok-image-cli":                   "AI Image Generation",
    "grok-imagine-image-pro":           "AI Image Generation",
    "heygen-avatar-lite":               "AI Video Generation",
    "image-detection":                  "AI Image Generation",
    "image-hosting":                    "Cloud Infrastructure",
    "image-magik-resize":               "AI Image Generation",
    "mindmap-generator":                "Productivity Automation",
    "ocr-python":                       "Document Processing",
    "openai-image-cli":                 "AI Image Generation",
    "photoshop-automator":              "AI Image Generation",
    "qr-gen":                           "Developer Tooling",
    "siliconflow-image-gen":            "AI Image Generation",
    "sprite-animator":                  "Game Development",
    "svg-to-image":                     "AI Image Generation",
    "tesseract-ocr":                    "Document Processing",
    "unsplash":                         "AI Image Generation",
    "visualization":                    "Data Analytics",
    "youtube-thumbnail-generation":     "AI Image Generation",
    "zhipu-cogview-image":              "AI Image Generation",
    "gamma":                            "Productivity Automation",
    "google-imagen-3-portrait-photography": "AI Image Generation",
    "comfy-ui":                         "AI Image Generation",
    "comfy-cli":                        "AI Image Generation",

    # === PDF & Documents ===
    "documents":                        "Document Processing",
    "ocr":                              "Document Processing",
    "markdown":                         "Document Automation",
    "excel":                            "Spreadsheet Automation",
    "sheets":                           "Spreadsheet Automation",
    "add-watermark-to-pdf":             "Document Processing",
    "ai-pdf-builder":                   "Document Processing",
    "attendance-sheet":                 "Document Processing",
    "beautiful-mermaid":                "Document Automation",
    "book-reader":                      "Document Processing",
    "bookkeeping-basics":               "Accounting Automation",
    "office-document-editor":           "Document Processing",
    "pdf-and-documents":                "Document Processing",
    "stirling-pdf":                     "Document Processing",
    "tiangong-wps-word-automation":     "Document Processing",
    "wps-skill":                        "Document Processing",
    "blankfiles":                       "Document Processing",
    "md2pdf-converter":                 "Document Processing",
    "md2pdf-xelatex":                   "Document Processing",
    "md-table-image":                   "Document Processing",
    "file-to-markdown":                 "Document Processing",
    "ogt-docs-create":                  "Document Processing",
    "anydocs":                          "Document Processing",
    "pls-office-docs":                  "Document Processing",
    "book-cover-generation":            "AI Image Generation",
    "certificate-generation":           "Document Processing",
    "agent-constitution":               "Document Processing",

    # === Transportation ===
    "travel":                           "Travel Automation",
    "flight":                           "Travel Automation",
    "uber":                             "Transportation",
    "maps":                             "Geolocation & Mapping",
    "google-maps":                      "Geolocation & Mapping",
    "amadeus-flights":                  "Travel Automation",
    "airfrance-afkl":                   "Travel Automation",
    "al-khanjry-bus":                   "Transportation",
    "bahn":                             "Transportation",
    "aviationstack-flight-tracker":     "Travel Automation",
    "aviation-weather":                 "Travel Automation",
    "flight-pricer":                    "Travel Automation",
    "flights-search":                   "Travel Automation",
    "google-flights":                   "Travel Automation",
    "hotel-pricer":                     "Travel Automation",
    "ryanair-fare-finder":              "Travel Automation",
    "tcom-tripgenie-skill":             "Travel Automation",
    "travel-price-drop-visa-reminder":  "Travel Automation",
    "variflight":                       "Travel Automation",
    "race-finder":                      "Travel Automation",
    "aihotel":                          "Travel Automation",
    "camino-hotel-finder":              "Travel Automation",
    "daolv-hotel-booking":              "Travel Automation",
    "daolv-hotel-search":               "Travel Automation",

    # === Media & Streaming ===
    "spotify":                          "Media & Entertainment",
    "youtube":                          "Media & Entertainment",
    "apple-music":                      "Media & Entertainment",
    "streaming":                        "Media & Entertainment",
    "anime":                            "Media & Entertainment",
    "anime-lookup":                     "Media & Entertainment",
    "audio-cog":                        "Media Automation",
    "audio-transcribe":                 "AI Voice & Audio",
    "lyrion-music-skill":               "Media & Entertainment",
    "plex-ctl":                         "Media & Entertainment",
    "roku-control":                     "Media & Entertainment",
    "soundcloud-watcher":               "Media & Entertainment",
    "suno-music":                       "Media & Entertainment",
    "alexa-control":                    "Smart Home Automation",
    "apple-media":                      "Media & Entertainment",
    "betbud-prediction-skill":          "Media & Entertainment",
    "blucli":                           "Media & Entertainment",
    "appletv":                          "Media & Entertainment",
    "ace-music":                        "Media & Entertainment",
    "podsips-search":                   "Media & Entertainment",

    # === Calendar & Scheduling ===
    "google-calendar":                  "Calendar Automation",
    "apple-calendar":                   "Calendar Automation",
    "scheduling":                       "Calendar Automation",
    "advanced-calendar":                "Calendar Automation",
    "apple-reminders":                  "Calendar Automation",
    "brainz-calendar":                  "Calendar Automation",
    "calcurse":                         "Calendar Automation",
    "calendar-scheduling":              "Calendar Automation",
    "caldav-calendar":                  "Calendar Automation",
    "caldav-cli":                       "Calendar Automation",
    "caldav-skill":                     "Calendar Automation",
    "icalendar-sync":                   "Calendar Automation",
    "icloud-caldav":                    "Calendar Automation",
    "icloud-reminders":                 "Calendar Automation",
    "m365-calendar":                    "Calendar Automation",
    "opencal":                          "Calendar Automation",
    "porteden-calendar":                "Calendar Automation",
    "cozi":                             "Calendar Automation",
    "belong-events":                    "Calendar Automation",
    "ai-meeting-scheduling":            "Calendar Automation",

    # === Apple Apps & Services ===
    "apple":                            "Apple Platform Development",
    "macos":                            "System Automation",
    "ios":                              "iOS Development",
    "shortcuts":                        "System Automation",
    "xcode":                            "iOS Development",
    "apple-contacts":                   "Apple Platform Development",
    "apple-find-my-local":              "Apple Platform Development",
    "apple-health-skill":               "Health Automation",
    "apple-mail-search":                "Email Automation",
    "apple-notes":                      "Knowledge Management",
    "apple-photos":                     "Media Automation",
    "apple-remind-me":                  "Task Management",
    "apple-search-ads-skill":           "Digital Marketing",
    "apple-watch":                      "Health Automation",
    "callmac":                          "Apple Platform Development",
    "drafts":                           "Knowledge Management",
    "findmy-location":                  "Geolocation & Mapping",
    "pear-apple":                       "Apple Platform Development",
    "pear-icloud":                      "Apple Platform Development",
    "alter-actions":                    "Apple Platform Development",

    # === Speech & Transcription ===
    "whisper":                          "AI Voice & Audio",
    "assemblyai":                       "AI Voice & Audio",
    "deepgram":                         "AI Voice & Audio",
    "transcription":                    "AI Voice & Audio",
    "eleven-labs":                      "AI Voice & Audio",
    "aliyun-asr":                       "AI Voice & Audio",
    "aliyun-tts":                       "AI Voice & Audio",
    "asr":                              "AI Voice & Audio",
    "azure-ai-transcription-py":        "AI Voice & Audio",
    "azure-ai-voicelive-py":            "AI Voice & Audio",
    "comfyui-tts":                      "AI Voice & Audio",
    "fish-tts":                         "AI Voice & Audio",
    "jetson-cuda-voice":                "AI Voice & Audio",
    "local-voice":                      "AI Voice & Audio",
    "local-vosk":                       "AI Voice & Audio",
    "macos-local-voice":                "AI Voice & Audio",
    "mh-openai-whisper":                "AI Voice & Audio",
    "mh-openai-whisper-api":            "AI Voice & Audio",
    "miranda-elevenlabs-speech":        "AI Voice & Audio",
    "mlx-local-inference":              "Local AI Development",
    "qwen3-tts-instruct":               "AI Voice & Audio",
    "qwen3-tts-local-inference":        "AI Voice & Audio",
    "sarvam":                           "AI Voice & Audio",
    "sam-tts":                          "AI Voice & Audio",
    "speakturbo-tts":                   "AI Voice & Audio",
    "telegram-voice-to-voice-macos":    "AI Voice & Audio",
    "togetherai-tts":                   "AI Voice & Audio",
    "valtec-tts":                       "AI Voice & Audio",
    "voice-recognition":                "AI Voice & Audio",
    "voice-memo":                       "AI Voice & Audio",
    "zai-tts":                          "AI Voice & Audio",
    "zhipu-asr":                        "AI Voice & Audio",
    "zhipu-tts":                        "AI Voice & Audio",
    "zvukogram":                        "AI Voice & Audio",
    "lh-edge-tts":                      "AI Voice & Audio",
    "beware-piper-tts":                 "AI Voice & Audio",
    "local-llama-tts":                  "Local AI Development",

    # === Notes & PKM ===
    "obsidian":                         "Knowledge Management",
    "roam":                             "Knowledge Management",
    "logseq":                           "Knowledge Management",
    "mem":                              "Knowledge Management",
    "notes":                            "Knowledge Management",
    "anki-connect":                     "Knowledge Management",
    "arc-wake-state":                   "Knowledge Management",
    "bear-notes":                       "Knowledge Management",
    "better-notion":                    "Knowledge Management",
    "bookstack":                        "Knowledge Management",
    "braindb":                          "Knowledge Management",
    "brainrepo":                        "Knowledge Management",
    "cairn-cli":                        "Knowledge Management",
    "chaos-mind":                       "Knowledge Management",
    "claw-roam":                        "Knowledge Management",
    "context-anchor":                   "Knowledge Management",
    "craft-do":                         "Knowledge Management",
    "cubox":                            "Knowledge Management",
    "daily-memory-save":                "Knowledge Management",
    "dev-chronicle":                    "Knowledge Management",
    "elite-longterm-memory":            "Knowledge Management",
    "fabric-api":                       "Knowledge Management",
    "feishu-memory-recall":             "Knowledge Management",
    "flomo-notes":                      "Knowledge Management",
    "fsxmemory":                        "Knowledge Management",
    "guava-memory":                     "Knowledge Management",
    "infinite-memory-v2":               "Knowledge Management",
    "mh-bear-notes":                    "Knowledge Management",
    "meeting-notes":                    "Productivity Automation",
    "meeting-to-action":                "Productivity Automation",
    "memo-persistent-memory":           "Knowledge Management",
    "muninn":                           "Knowledge Management",
    "muninn-memory":                    "Knowledge Management",
    "nosi":                             "Knowledge Management",
    "save-to-obsidian":                 "Knowledge Management",
    "session-memory":                   "Knowledge Management",
    "soul-framework":                   "Knowledge Management",
    "upnote":                           "Knowledge Management",
    "voice-notes-pro":                  "Knowledge Management",
    "enhanced-memory":                  "Knowledge Management",
    "cortex-memory":                    "Knowledge Management",
    "simplemem":                        "Knowledge Management",
    "supermemory-free":                 "Knowledge Management",
    "total-recall":                     "Knowledge Management",
    "trilium":                          "Knowledge Management",

    # === Security & Passwords ===
    "1password":                        "Security & Credentials",
    "bitwarden":                        "Security & Credentials",
    "security":                         "Cybersecurity",
    "anchain":                          "Blockchain Security",
    "ecap-security":                    "Cybersecurity",
    "bitwarden-vault":                  "Security & Credentials",
    "cifer-sdk":                        "Cybersecurity",
    "cifer-security":                   "Cybersecurity",
    "clawaudit":                        "Cybersecurity",
    "clawdstrike":                      "Cybersecurity",
    "credential-manager":               "Security & Credentials",
    "dashlane":                         "Security & Credentials",
    "domain-trust-check":               "Cybersecurity",
    "expanso-tls-inspect":              "Cybersecurity",
    "ggshield-scanner":                 "Cybersecurity",
    "go-security-vulnerability":        "Cybersecurity",
    "guardian-angel":                   "Cybersecurity",
    "hash-toolkit":                     "Cybersecurity",
    "leak":                             "Cybersecurity",
    "mfa-word":                         "Security & Credentials",
    "api-security":                     "Cybersecurity",
    "safe-encryption-skill":            "Cybersecurity",
    "saysigned":                        "Security & Credentials",
    "secure-auth-patterns":             "Security & Credentials",
    "sovereign-api-hardener":           "Cybersecurity",
    "twhidden-bitwarden":               "Security & Credentials",
    "vnsh":                             "Cybersecurity",
    "passwordstore-broker":             "Security & Credentials",
    "qr-password":                      "Security & Credentials",
    "adguard":                          "Cybersecurity",

    # === Smart Home & IoT ===
    "philips-hue":                      "Smart Home Automation",
    "homekit":                          "Smart Home Automation",
    "home-assistant":                   "Smart Home Automation",
    "smart-home":                       "Smart Home Automation",
    "iot":                              "IoT Development",
    "anova-oven":                       "Smart Home Automation",
    "bambu-cli":                        "Smart Home Automation",
    "bambu-local":                      "Smart Home Automation",
    "beestat":                          "Smart Home Automation",
    "control-ikea-lightbulb":           "Smart Home Automation",
    "devialet":                         "Smart Home Automation",
    "dht11-temp":                       "IoT Development",
    "dirigera-control":                 "Smart Home Automation",
    "dyson-cli":                        "Smart Home Automation",
    "eightctl":                         "Smart Home Automation",
    "farmos-weather":                   "IoT Development",
    "frigate":                          "Smart Home Automation",
    "glitch-homeassistant":             "Smart Home Automation",
    "google-home":                      "Smart Home Automation",
    "govee-lights":                     "Smart Home Automation",
    "homebridge":                       "Smart Home Automation",
    "homey":                            "Smart Home Automation",
    "homey-cli":                        "Smart Home Automation",
    "ipcam":                            "Smart Home Automation",
    "lg-thinq":                         "Smart Home Automation",
    "nest-sdm":                         "Smart Home Automation",
    "openmeteo-sh-weather-simple":      "Smart Home Automation",
    "tempest-weather":                  "Smart Home Automation",
    "tasmota-skill":                    "IoT Development",
    "wiz-light-control":                "Smart Home Automation",
    "xiaomi-air-purifier":              "Smart Home Automation",
    "lifx":                             "Smart Home Automation",
    "node-red-manager":                 "IoT Development",

    # === Gaming ===
    "unity":                            "Game Development",
    "godot":                            "Game Development",
    "steam":                            "Gaming",
    "game":                             "Game Development",
    "arena":                            "Gaming",
    "brawlnet":                         "Gaming",
    "clawtopia":                        "Gaming",
    "clawville":                        "Gaming",
    "deepclaw":                         "Gaming",
    "hivemind":                         "Gaming",
    "hytale":                           "Game Development",
    "kradleverse-act":                  "Gaming",
    "kradleverse-init":                 "Gaming",
    "lobster-trap":                     "Gaming",
    "sovereign-rpg-xp-engine":          "Game Development",
    "sprite-sheet":                     "Game Development",
    "agentgram":                        "Social Media Automation",
    "android-3d-developer":             "Game Development",
    "openbotcity":                      "Gaming",

    # === CLI Utilities ===
    "shell":                            "System Automation",
    "bash":                             "System Automation",
    "terminal":                         "System Automation",
    "filesystem":                       "System Automation",
    "atxp":                             "AI Agent Development",
    "13-day-sprint-method":             "Productivity Automation",
    "activity-analyzer":                "Product Analytics",
    "agent-hardening":                  "Cybersecurity",
    "agent-mbti":                       "Productivity Automation",
    "agent-rate-limiter":               "Developer Tooling",
    "agents-skill-security-audit":      "Cybersecurity",
    "ahc-automator":                    "System Automation",
    "airfoil":                          "Media Automation",
    "aria2-json-rpc":                   "System Automation",
    "askhuman":                         "Productivity Automation",
    "bandwidth-income":                 "System Automation",
    "claude-cost-cli":                  "Developer Tooling",
    "claude-usage-cli":                 "Developer Tooling",
    "filesystem-mcp":                   "System Automation",
    "win-terminal":                     "System Automation",
    "arc-memory-pruner":                "Developer Tooling",
    "argus-edge":                       "Cybersecurity",

    # === ClawdBot Tools ===
    "adhd-assistant":                   "Productivity Automation",
    "agent-builder":                    "AI Agent Development",
    "agents-manager":                   "AI Agent Development",
    "assimilate-mcp":                   "Developer Tooling",
    "birthday-reminder":                "Productivity Automation",
    "bluebubbles":                      "Bot Development",
    "captchas-openclaw":                "Web Automation",
    "claude-connect":                   "AI Development",
    "clauditor":                        "Developer Tooling",
    "claw-face":                        "AI Development",
    "clawd-coach":                      "Personal Development",
    "clawd-modifier":                   "AI Development",
    "clawd-presence":                   "Developer Tooling",
    "clawdbot-security-check":          "Cybersecurity",
    "clawdbot-skill-update":            "Developer Tooling",
    "clawdbot-sync":                    "Developer Tooling",
    "clawdbot-macos-build":             "Developer Tooling",
    "claude-code-usage":                "Developer Tooling",

    # === iOS/macOS Development ===
    "apple-docs":                       "iOS Development",
    "brew-audit":                       "Developer Tooling",
    "ios-simulator":                    "iOS Development",
    "lulu-monitor":                     "System Automation",
    "mac-clean-skill":                  "System Automation",
    "mac-power-tools":                  "System Automation",
    "macos-spm-app-packaging":          "iOS Development",
    "instruments-profiling":            "iOS Development",
    "swift-concurrency-expert":         "iOS Development",
    "sfsymbol-generator":               "iOS Development",
    "pagerkit":                         "iOS Development",
    "riskofficer":                      "iOS Development",

    # === Moltbook ===
    "moltbook":                         "Social Platform Integration",
    "moltbook-interact":                "Social Platform Integration",
    "social":                           "Social Media Automation",
    "agentchat":                        "Team Communication",
    "agentgram-openclaw":               "Social Media Automation",
    "clankedin":                        "Professional Network Automation",
    "clawork":                          "Productivity Automation",
    "crustafarian":                     "Social Platform Integration",
    "ghl-open-account":                 "CRM & Sales",
    "imagemagick":                      "AI Image Generation",
    "joko-moltbook":                    "Social Platform Integration",
    "mailchannels":                     "Email Marketing",
    "molt-life-kernel":                 "Social Platform Integration",
    "molt-trust":                       "Social Platform Integration",
    "elevenlabs-open-account":          "AI Voice & Audio",
    "ez-cronjob":                       "Workflow Automation",
    "fieldy-ai-webhook":                "Workflow Automation",
    "mersal":                           "Communication Automation",

    # === Self-hosted & Automation ===
    "n8n":                              "Workflow Automation",
    "activepieces":                     "Workflow Automation",
    "beacon":                           "Self-hosted Infrastructure",
    "bridle":                           "Workflow Automation",
    "casual-cron":                      "Workflow Automation",
    "claw-sync":                        "Self-hosted Infrastructure",
    "cron-backup":                      "Workflow Automation",
    "cron-retry":                       "Workflow Automation",
    "fast-io":                          "Self-hosted Infrastructure",
    "fathom":                           "Product Analytics",
    "frappecli":                        "Self-hosted Infrastructure",
    "freshrss-reader":                  "Web Research",
    "gotify":                           "Bot Development",
    "hydra-evolver":                    "AI Agent Development",
    "keepmyclaw":                       "Self-hosted Infrastructure",
    "looper-golf":                      "Workflow Automation",
    "meetgeek":                         "Productivity Automation",
    "mongodb-atlas-admin":              "Database Engineering",
    "multiple-personas":                "AI Development",
    "reef-n8n-automation":              "Workflow Automation",
    "self-hosted-and-automation":       "Self-hosted Infrastructure",

    # === Data & Analytics ===
    "data":                             "Data Analysis",
    "analytics":                        "Product Analytics",
    "pandas":                           "Data Analysis",
    "database":                         "Database Engineering",
    "postgres":                         "Database Engineering",
    "sqlite":                           "Database Engineering",
    "mongodb":                          "Database Engineering",
    "redis":                            "Backend Engineering",
    "supabase":                         "Database Engineering",
    "amplitude-automation":             "Product Analytics",
    "canva":                            "UI/UX Design",
    "check-analytics":                  "Product Analytics",
    "cicd-pipeline":                    "CI/CD Pipelines",
    "csv-pipeline":                     "Data Analysis",
    "daily-report":                     "Data Analysis",
    "data-analyst":                     "Data Analysis",
    "data-enricher":                    "Data Analysis",
    "data-lineage-tracker":             "Data Analysis",
    "duckdb-en":                        "Data Analysis",
    "facebook-page-manager":            "Social Media Automation",
    "google-analytics-api":             "Product Analytics",
    "sql-server-toolkit":               "Database Engineering",
    "add-analytics":                    "Product Analytics",

    # === Blockchain & Web3 ===
    "defi-mcp":                         "Blockchain Development",
    "alchemy":                          "Blockchain Development",
    "moralis":                          "Blockchain Development",
    "solana":                           "Blockchain Development",
    "ethereum":                         "Blockchain Development",
    "crypto":                           "Crypto Exchange Integration",
    "nft":                              "NFT & Web3",
    "aave-liquidation-monitor":         "Blockchain Development",
    "beaconchain":                      "Blockchain Development",
    "coingecko-price":                  "Crypto Exchange Integration",
    "doginals":                         "NFT & Web3",
    "ethereum-history":                 "Blockchain Development",
    "hedera-tx-builder":                "Blockchain Development",
    "kamino-positions-monitor":         "Blockchain Development",
    "lifi-crosschain":                  "Blockchain Development",
    "mintclub":                         "NFT & Web3",
    "morpho-earn":                      "Blockchain Development",
    "near-batch-sender":                "Blockchain Development",
    "pump-fun":                         "NFT & Web3",
    "renzo":                            "Blockchain Development",
    "slv-grpc-geyser":                  "Blockchain Development",
    "slv-rpc":                          "Blockchain Development",
    "slv-validator":                    "Blockchain Development",
    "solana-scanner":                   "Blockchain Development",
    "solana-skills":                    "Blockchain Development",
    "solana-swaps":                     "Blockchain Development",
    "sui":                              "Blockchain Development",
    "token-alert":                      "Crypto Exchange Integration",
    "token-guard":                      "Crypto Exchange Integration",
    "torchliquidationbot":              "Blockchain Development",
    "torchmarket":                      "Blockchain Development",
    "unclaimed-sol-scanner":            "Blockchain Development",
    "xian-node-skill":                  "Blockchain Development",
    "xrpl-tx-builder":                  "Blockchain Development",
    "zapper":                           "Blockchain Development",
    "zapper-api":                       "Blockchain Development",

    # === Finance ===
    "stripe":                           "Payment Integration",
    "plaid":                            "Fintech Integration",
    "quickbooks":                       "Accounting Automation",
    "aisa-financial-data":              "Data Analysis",
    "aisa-financial-data-api":          "Data Analysis",
    "aisa-market-skill":                "Data Analysis",
    "bank-skills":                      "Fintech Integration",
    "bexio":                            "Accounting Automation",
    "bookkeeper":                       "Accounting Automation",
    "fear-and-greed-index":             "Data Analysis",
    "finance-news":                     "Research & Analysis",
    "finance-skill":                    "Fintech Integration",
    "finance-watcher":                  "Fintech Integration",
    "financial-calculator":             "Fintech Integration",
    "financial-reconciler":             "Accounting Automation",
    "finviz-crawler":                   "Data Analysis",
    "fints-banking":                    "Fintech Integration",
    "fred-navigator":                   "Research & Analysis",
    "invoice-tracker-pro":              "Accounting Automation",
    "jquants-mcp":                      "Data Analysis",
    "moonbanking":                      "Fintech Integration",
    "moonpay":                          "Payment Integration",
    "mt5trade":                         "Fintech Integration",
    "paypilot-agms":                    "Payment Integration",
    "portfolio-risk-analyzer":          "Data Analysis",
    "quickbooks-online":                "Accounting Automation",
    "revolut-business":                 "Fintech Integration",
    "rsoft-agentic-bank":               "Fintech Integration",
    "smartbill":                        "Accounting Automation",
    "smartbill-invoicing":              "Accounting Automation",
    "starling-bank":                    "Fintech Integration",
    "teller-borrow":                    "Fintech Integration",
    "vibetrader":                       "Fintech Integration",
    "vynn-backtester":                  "Data Analysis",

    # === E-Commerce ===
    "amazon-orders":                    "E-Commerce Automation",
    "amazon-competitor-analyzer":       "E-Commerce Automation",
    "clawpify":                         "E-Commerce Automation",
    "clawver-digital-products":         "E-Commerce Automation",
    "clawver-reviews":                  "E-Commerce Automation",
    "csfloat":                          "E-Commerce Automation",
    "eachlabs-product-visuals":         "E-Commerce Automation",
    "forage-shopping":                  "E-Commerce Automation",
    "idealista":                        "E-Commerce Automation",
    "listonic":                         "E-Commerce Automation",
    "marktplaats":                      "E-Commerce Automation",
    "naver-shopping":                   "E-Commerce Automation",
    "priceforagent":                    "E-Commerce Automation",
    "scout-commerce":                   "E-Commerce Automation",
    "shopify-bulk-upload":              "E-Commerce Automation",
    "stock-price-checker":              "E-Commerce Automation",
    "supermarket":                      "E-Commerce Automation",
    "tradekix":                         "E-Commerce Automation",
    "amazon-data":                      "E-Commerce Automation",

    # === Personal Development ===
    "adaptive-learning-agents":         "Personal Development",
    "adaptivetest":                     "Personal Development",
    "adhd-body-doubling":               "Personal Development",
    "adversarial-coach":                "Personal Development",
    "agent-evolver":                    "Personal Development",
    "agent-reflect":                    "Personal Development",
    "anxiety-relief":                   "Personal Development",
    "beaverhabits":                     "Personal Development",
    "brw-case-study-builder":           "Research & Analysis",
    "cedh-advisor":                     "Personal Development",
    "clawcierge":                       "Personal Development",
    "crucial-conversations-coach":      "Personal Development",
    "daily-questions":                  "Personal Development",
    "daily-review-ritual":              "Personal Development",
    "deepthink":                        "Personal Development",
    "endurance-coach":                  "Health Automation",
    "lofy-life-coach":                  "Personal Development",
    "lofy-career":                      "Personal Development",
    "solo-retro":                       "Personal Development",
    "solo-review":                      "Personal Development",

    # === Health & Fitness ===
    "calorie-counter":                  "Health Automation",
    "diet-tracker":                     "Health Automation",
    "detox-counter":                    "Health Automation",
    "fasting-tracker":                  "Health Automation",
    "fitbit":                           "Health Automation",
    "fitbit-analytics":                 "Health Automation",
    "garmin-cli":                       "Health Automation",
    "garmin-health":                    "Health Automation",
    "health-guardian":                  "Health Automation",
    "health-summary":                   "Health Automation",
    "health-sync":                      "Health Automation",
    "hevy":                             "Health Automation",
    "huckleberry":                      "Health Automation",
    "intervals-icu":                    "Health Automation",
    "mealie-api":                       "Health Automation",
    "muscle-gain":                      "Health Automation",
    "oura-analytics":                   "Health Automation",
    "recipe-finder":                    "Health Automation",
    "samsung-health":                   "Health Automation",
    "sauna-calm":                       "Health Automation",
    "cook":                             "Health Automation",
    "calorie-visualizer":               "Health Automation",
    "bring-recipes":                    "Health Automation",
}


_OPENCLAW_CAPABILITY_SKILLS: dict[str, tuple[str, str, str]] = {
    # === GitHub & Git ===
    "github":               ("github",          "GitHub Automation via OpenClaw",              "A"),
    "github-manager":       ("github",          "GitHub PR & Repo Management",                 "A"),
    "pr-reviewer":          ("github",          "Automated PR Review",                         "A"),
    "code-runner":          ("github",          "Sandboxed Code Execution",                    "B"),
    "auto-pr-merger":       ("github",          "Auto-Merge PRs After CI Pass",                "B"),
    "git-essentials":       ("github",          "Git Essentials (commit, push, log)",           "B"),
    "conventional-commits": ("github",          "Conventional Commit Enforcement",             "C"),
    "super-github":         ("github",          "Super GitHub (PRs, issues, releases)",        "A"),
    "pr-risk-analyzer":     ("github",          "PR Risk Analysis & Review",                   "B"),
    "repo-pr-triage":       ("github",          "Automated PR Triage",                         "B"),
    "gh-action-gen":        ("github",          "GitHub Actions Workflow Generator",           "B"),
    "azure-devops":         ("azure",           "Azure DevOps Pipeline Automation",            "B"),
    "bitbucket-automation": ("bitbucket",       "Bitbucket Automation & PR Management",        "B"),

    # === Browser & Web Automation ===
    "browser":              ("puppeteer",       "Browser Automation & Interaction",            "A"),
    "agent-browser":        ("puppeteer",       "Agent-first Web Browser Control",             "A"),
    "screenshot":           ("puppeteer",       "Automated Screenshot Capture",                "B"),
    "browser-use":          ("puppeteer",       "AI-driven Browser Use Agent",                 "A"),
    "cdp-browser":          ("puppeteer",       "Chrome DevTools Protocol Browser",            "A"),
    "camoufox":             ("puppeteer",       "Anti-detection Browser Automation",           "B"),
    "2captcha":             ("custom",          "CAPTCHA Solving Automation",                  "B"),
    "anycrawl":             ("firecrawl",       "Universal Web Crawler",                       "B"),
    "accessibility-toolkit":("custom",          "Web Accessibility Audit & Fix",               "C"),
    "automation-workflows": ("n8n",             "Visual Automation Workflow Builder",          "B"),

    # === Search & Research ===
    "web-search":           ("brave-search",    "Web Search Automation",                       "B"),
    "web-search-pro":       ("tavily",          "Advanced AI Web Search",                      "A"),
    "serpapi-mcp":          ("serp-api",        "SERP API Search Integration",                 "B"),
    "firecrawl":            ("firecrawl",       "Web Scraping via OpenClaw",                   "A"),
    "tavily":               ("tavily",          "Tavily Research Agent",                       "A"),
    "exa":                  ("exa",             "Semantic Web Search",                         "A"),
    "exa-tool":             ("exa",             "Exa Neural Search Tool",                      "A"),
    "perplexity":           ("perplexity",      "Perplexity AI Research",                      "B"),
    "perplexity-deep-search":("perplexity",     "Perplexity Deep Research Mode",               "A"),
    "brave":                ("brave-search",    "Brave Search API Integration",                "B"),
    "ahrefs-mcp":           ("ahrefs",          "Ahrefs SEO & Backlink Analysis",              "A"),
    "academic-deep-research":("custom",         "Academic Deep Research Agent",                "A"),
    "arxiv-paper-processor":("custom",          "arXiv Paper Processing & Summarization",      "A"),
    "arxiv-cli-tools":      ("custom",          "arXiv CLI Search & Download",                 "B"),
    "baidu-search":         ("custom",          "Baidu Search Integration",                    "C"),
    "bing-search":          ("custom",          "Bing Search API Integration",                 "B"),
    "openclaw-free-web-search":("brave-search", "OpenClaw Free Web Search",                    "B"),
    "rss-skill":            ("custom",          "RSS Feed Aggregation & Processing",           "C"),
    "google-trends":        ("custom",          "Google Trends Analysis",                      "B"),
    "wolfram-alpha":        ("custom",          "Wolfram Alpha Computation Engine",            "B"),
    "hybrid-deep-search":   ("tavily",          "Multi-source Hybrid Deep Search",             "A"),
    "super-research":       ("tavily",          "Multi-LLM Super Research Agent",              "S"),
    "law-search":           ("custom",          "Legal Document & Case Research",              "B"),
    "pubmed-edirect":       ("custom",          "PubMed Literature Search",                    "B"),
    "yacy":                 ("custom",          "Decentralized Web Search (YaCy)",             "C"),

    # === HTTP & API Integration ===
    "http-request":         ("custom",          "Arbitrary HTTP API Calls",                    "B"),
    "cron-scheduler":       ("automation",      "Scheduled Task Automation",                   "B"),
    "cron":                 ("automation",      "Cron-based Task Scheduling",                  "C"),
    "api-dev":              ("custom",          "API Development & Testing",                   "B"),
    "agentapi":             ("custom",          "Agent API Bridge",                            "B"),
    "coingecko":            ("coingecko",       "CoinGecko Crypto Price Data",                 "B"),

    # === Productivity & Knowledge ===
    "notion":               ("notion",          "Notion Workspace Automation",                 "B"),
    "better-notion":        ("notion",          "Enhanced Notion Integration",                 "B"),
    "obsidian":             ("obsidian",        "Obsidian Knowledge Base Automation",          "B"),
    "save-to-obsidian":     ("obsidian",        "Save Content to Obsidian Vault",              "B"),
    "logseq":               ("logseq",          "Logseq Graph Database Automation",            "B"),
    "anki-connect":         ("anki",            "Anki Flashcard Automation",                   "B"),
    "capacities":           ("notion",          "Capacities Knowledge Graph",                  "B"),
    "supermemory-free":     ("custom",          "Supermemory Persistent Agent Memory",         "B"),
    "cortex-memory":        ("custom",          "Cortex Long-term Agent Memory",               "B"),
    "enhanced-memory":      ("custom",          "Enhanced Persistent Memory System",           "B"),
    "simplemem":            ("custom",          "Simple Persistent Memory Store",              "C"),
    "total-recall":         ("custom",          "Total Recall Memory Agent",                   "B"),
    "braindb":              ("custom",          "Brain Knowledge Database",                    "B"),
    "elite-longterm-memory":("custom",          "Elite Long-term Agent Memory",                "A"),
    "fabric-api":           ("custom",          "Fabric Pattern Library API",                  "B"),

    # === File & Document Processing ===
    "file-manager":         ("filesystem",      "Local File System Management",                "C"),
    "pdf-reader":           ("pdf",             "PDF Reading & Extraction",                    "B"),
    "pdf":                  ("pdf",             "PDF Processing & Generation",                 "B"),
    "stirling-pdf":         ("pdf",             "Stirling PDF All-in-One Toolkit",             "A"),
    "md2pdf-converter":     ("pdf",             "Markdown to PDF Converter",                   "C"),
    "ocr":                  ("custom",          "OCR Text Extraction from Images",             "B"),
    "tesseract-ocr":        ("custom",          "Tesseract OCR Engine",                        "B"),
    "ocr-python":           ("custom",          "Python OCR Automation",                       "B"),
    "azure-doc-ocr":        ("azure",           "Azure Document OCR Service",                  "B"),
    "excel-workflow":       ("custom",          "Excel Automation & Data Processing",          "B"),
    "ppt-ooxml-tool":       ("custom",          "PowerPoint OOXML Automation",                 "B"),
    "office-document-editor":("custom",         "Office Document Editing Automation",          "B"),
    "tiangong-wps-word-automation":("custom",   "WPS Word Document Automation",                "B"),
    "blankfiles":           ("filesystem",      "File Creation & Template Automation",         "C"),
    "anydocs":              ("custom",          "Universal Document Processing",               "B"),

    # === Email & Communication ===
    "email-drafter":        ("gmail",           "AI Email Drafting & Sending",                 "B"),
    "gmail":                ("gmail",           "Gmail Automation via OpenClaw",               "B"),
    "gmail-oauth":          ("gmail",           "Gmail OAuth2 Full Access",                    "B"),
    "gmail-tool":           ("gmail",           "Gmail Search & Label Management",             "B"),
    "gmail-secretary":      ("gmail",           "Gmail AI Secretary Agent",                    "A"),
    "email-autoreply":      ("gmail",           "Auto-Reply Email Agent",                      "B"),
    "email-manager-ai":     ("gmail",           "AI Email Manager & Triage",                   "B"),
    "clawemail":            ("gmail",           "ClawEmail Multi-account Manager",             "B"),
    "custom-smtp-sender":   ("sendgrid",        "Custom SMTP Email Sender",                    "C"),
    "lnemail":              ("sendgrid",        "Low-noise Email Notification Engine",         "C"),
    "sendgrid":             ("sendgrid",        "SendGrid Email Marketing API",                "B"),
    "mailchimp":            ("mailchimp",        "Mailchimp Email Campaigns",                   "B"),
    "brevo":                ("sendgrid",        "Brevo (Sendinblue) Email API",                "B"),
    "mailchannels":         ("sendgrid",        "MailChannels Transactional Email",            "B"),
    "slack":                ("slack",           "Slack Messaging via OpenClaw",                "B"),
    "discord":              ("discord",         "Discord Bot via OpenClaw",                    "B"),
    "telegram":             ("telegram",        "Telegram Bot via OpenClaw",                   "B"),
    "whatsapp":             ("telegram",        "WhatsApp Automation via OpenClaw",            "B"),
    "twilio":               ("twilio",          "Twilio SMS & Voice via OpenClaw",             "B"),
    "clawd-talk":           ("twilio",          "Phone Calling & SMS (Telnyx)",                "B"),
    "rocketchat":           ("slack",           "Rocket.Chat Team Communication",              "C"),
    "zulip":                ("slack",           "Zulip Open-source Team Chat",                 "C"),
    "intercom-conversations":("intercom",       "Intercom Customer Conversations",             "B"),
    "localsend":            ("custom",          "LocalSend P2P File & Message Transfer",       "C"),
    "outbound-call":        ("twilio",          "Outbound Phone Call Automation",              "B"),

    # === Calendar & Scheduling ===
    "calendar":             ("google-calendar", "Calendar Event Management",                   "B"),
    "google-calendar":      ("google-calendar", "Google Calendar Automation",                  "B"),
    "apple-calendar":       ("apple",           "Apple Calendar Automation",                   "B"),
    "apple-reminders":      ("apple",           "Apple Reminders Automation",                  "B"),
    "caldav-calendar":      ("google-calendar", "CalDAV Calendar Sync",                        "C"),
    "caldav-skill":         ("google-calendar", "CalDAV Protocol Integration",                 "C"),
    "cal-com-automation":   ("google-calendar", "Cal.com Scheduling Automation",               "B"),
    "icalendar-sync":       ("google-calendar", "iCalendar Feed Sync",                         "C"),
    "icloud-caldav":        ("apple",           "iCloud Calendar via CalDAV",                  "B"),
    "m365-calendar":        ("microsoft-graph", "Microsoft 365 Calendar",                      "B"),
    "ai-meeting-scheduling":("google-calendar", "AI-powered Meeting Scheduler",                "B"),
    "calcurse":             ("custom",          "Calcurse Terminal Calendar",                  "C"),

    # === AI Image Generation ===
    "image-gen":            ("stable-diffusion","AI Image Generation via OpenClaw",            "A"),
    "image-lab":            ("stable-diffusion","Advanced AI Image Lab",                       "A"),
    "stable-diffusion":     ("stable-diffusion","Stable Diffusion via OpenClaw",               "A"),
    "fal-ai":               ("fal",             "Fal.ai Fast Image Generation",                "A"),
    "fal-text-to-image":    ("fal",             "Fal.ai Text-to-Image API",                    "A"),
    "grok-image-cli":       ("custom",          "Grok Image Generation CLI",                   "B"),
    "eachlabs-image-generation":("custom",      "EachLabs AI Image Generation",                "A"),
    "eachlabs-image-edit":  ("custom",          "EachLabs AI Image Editing",                   "A"),
    "eachlabs-face-swap":   ("custom",          "EachLabs AI Face Swap",                       "A"),
    "openai-image-cli":     ("openai",          "OpenAI DALL-E Image Generation",              "A"),
    "azure-image-gen":      ("azure",           "Azure AI Image Generation",                   "B"),
    "zhipu-cogview-image":  ("custom",          "Zhipu CogView Image Generation",              "B"),
    "siliconflow-image-gen":("custom",          "SiliconFlow Image Generation",                "B"),
    "coloring-page":        ("stable-diffusion","Coloring Page Generator",                     "C"),
    "depth-map-generation": ("custom",          "AI Depth Map Generation",                     "B"),
    "unsplash":             ("custom",          "Unsplash Stock Image Search",                 "B"),
    "svg-to-image":         ("custom",          "SVG to Image Converter",                      "C"),
    "ascii-art-generator":  ("custom",          "ASCII Art Generator",                         "C"),
    "canva-connect":        ("canva",           "Canva Design Automation",                     "B"),
    "comfyui":              ("stable-diffusion","ComfyUI Advanced Image Pipeline",             "S"),
    "comfyui-imagegen":     ("stable-diffusion","ComfyUI Image Generation Node",              "A"),

    # === AI Video Generation ===
    "runway":               ("runway",          "AI Video Generation",                         "A"),
    "ai-video-gen":         ("runway",          "AI Video Generation Agent",                   "A"),
    "eachlabs-video-generation":("custom",      "EachLabs AI Video Generation",                "A"),
    "eachlabs-video-edit":  ("custom",          "EachLabs AI Video Editing",                   "A"),
    "heygen-avatar-lite":   ("custom",          "HeyGen AI Avatar Video",                      "A"),
    "ffmpeg-video-editor":  ("custom",          "FFmpeg Video Processing & Editing",           "B"),
    "captions":             ("custom",          "AI Video Captions Generator",                 "B"),
    "youtube-thumbnail-generation":("custom",   "AI YouTube Thumbnail Creator",                "B"),

    # === Speech & Audio ===
    "eleven-labs":          ("eleven-labs",     "AI Voice Synthesis via OpenClaw",             "A"),
    "whisper":              ("assemblyai",       "Whisper Speech-to-text",                      "B"),
    "assemblyai":           ("assemblyai",       "AssemblyAI Transcription",                    "B"),
    "deepgram":             ("assemblyai",       "Deepgram Real-time Transcription",            "A"),
    "mh-openai-whisper":    ("assemblyai",       "OpenAI Whisper via MH Integration",           "B"),
    "speakturbo-tts":       ("eleven-labs",      "SpeakTurbo Fast TTS Engine",                  "B"),
    "comfyui-tts":          ("eleven-labs",      "ComfyUI Text-to-Speech Node",                 "B"),
    "aliyun-tts":           ("custom",           "Alibaba Cloud TTS Service",                   "B"),
    "local-voice":          ("ollama",           "Local On-device Voice Synthesis",             "B"),
    "qwen3-tts-instruct":   ("custom",           "Qwen3 TTS Instruct Model",                    "B"),
    "sarvam":               ("custom",           "Sarvam Indian Language TTS/STT",              "B"),
    "voice-recognition":    ("assemblyai",       "Voice Recognition & Commands",                "B"),
    "audio-transcribe":     ("assemblyai",       "Audio File Transcription",                    "B"),
    "zai-tts":              ("eleven-labs",      "ZAI Text-to-Speech Engine",                   "B"),
    "zhipu-asr":            ("custom",           "Zhipu ASR Chinese Speech Recognition",        "B"),
    "zvukogram":            ("custom",           "Zvukogram Russian TTS Service",               "C"),

    # === CRM & Sales ===
    "hubspot":              ("hubspot",          "HubSpot CRM via OpenClaw",                    "B"),
    "salesforce":           ("salesforce",       "Salesforce CRM via OpenClaw",                 "B"),
    "attio-enhanced":       ("custom",           "Attio Modern CRM Automation",                 "B"),
    "attio-crm":            ("custom",           "Attio CRM CLI Integration",                   "B"),
    "apollo":               ("custom",           "Apollo.io Lead Enrichment & Outreach",        "A"),
    "lead-extractor":       ("custom",           "Web Lead Extraction Agent",                   "B"),
    "lead-generation":      ("custom",           "AI Lead Generation Pipeline",                 "B"),
    "cold-outreach":        ("gmail",            "Cold Outreach Email Sequences",               "B"),
    "cold-email":           ("gmail",            "Cold Email Campaign Automation",              "B"),
    "workcrm":              ("custom",           "WorkCRM Sales Pipeline",                      "B"),
    "ghl-crm-for-realtors": ("custom",           "GoHighLevel CRM for Real Estate",             "B"),

    # === Social Media ===
    "tweetclaw":            ("twitter",          "X/Twitter Automation (40+ endpoints)",        "A"),
    "x-twitter":            ("twitter",          "X/Twitter Scraping & Automation",             "A"),
    "linkedapi":            ("linkedin",         "LinkedIn Profile & Messaging Automation",     "A"),
    "bluesky":              ("custom",           "Bluesky Social Automation",                   "B"),
    "tiktok-viral-marketing":("custom",          "TikTok Viral Marketing Automation",           "B"),
    "tiktok-trend-challenger":("custom",         "TikTok Trend Research Agent",                 "B"),
    "instagram-search":     ("custom",           "Instagram Search & Monitoring",               "B"),
    "agentgram":            ("custom",           "AgentGram Social Media Platform",             "B"),
    "simplified-social-media":("custom",         "Simplified Social Media Manager",             "B"),
    "social-media-manager": ("custom",           "AI Social Media Manager",                     "B"),
    "facebook-page-manager":("custom",           "Facebook Page Management",                    "B"),
    "kiro-x-publisher":     ("twitter",          "X/Twitter Publisher via Kiro",                "B"),
    "twitter-x-api":        ("twitter",          "X/Twitter API Direct Access",                 "A"),

    # === DevOps & Cloud ===
    "docker":               ("docker",           "Docker Container Management",                 "B"),
    "kubernetes":           ("kubernetes",       "Kubernetes Orchestration",                    "A"),
    "aws":                  ("amazon-s3",        "AWS Services via OpenClaw",                   "A"),
    "vercel":               ("vercel",           "Vercel Deployment via OpenClaw",              "B"),
    "terraform":            ("custom",           "Terraform Infrastructure as Code",            "A"),
    "ansible-skill":        ("custom",           "Ansible Configuration Management",            "B"),
    "aws-security-scanner": ("amazon-s3",        "AWS Security Posture Scanner",                "B"),
    "sovereign-aws-cost-optimizer":("amazon-s3", "AWS Cost Optimization Agent",                 "B"),
    "railway-deploy":       ("custom",           "Railway.app Deployment Agent",                "B"),
    "hcloud":               ("custom",           "Hetzner Cloud Management",                    "B"),
    "tf-plan-review":       ("custom",           "Terraform Plan Review Agent",                 "B"),
    "neo-docker-to-k8s-manifests":("kubernetes", "Docker to Kubernetes Manifest Generator",    "B"),
    "neo-tf-module-generator":("custom",         "Terraform Module Generator",                  "B"),
    "ssh-op":               ("custom",           "SSH Operations Manager",                      "B"),
    "runpod":               ("custom",           "RunPod GPU Cloud Management",                 "A"),
    "rollbar":              ("custom",           "Rollbar Error Monitoring Integration",        "B"),
    "grafana-lens":         ("custom",           "Grafana Dashboard Automation",                "B"),
    "server-health-agent":  ("custom",           "Server Health Monitor Agent",                 "B"),
    "cloudflare-guard":     ("cloudflare",       "Cloudflare Security & DNS Manager",           "B"),
    "vault":                ("custom",           "HashiCorp Vault Secrets Manager",             "B"),

    # === AI Models & LLMs ===
    "claude-code":          ("anthropic",        "Claude Code Integration in OpenClaw",         "S"),
    "gemini":               ("anthropic",        "Gemini CLI & Google Search",                  "B"),
    "openai":               ("openai",           "OpenAI API via OpenClaw",                     "A"),
    "ollama":               ("ollama",           "Local LLM with Ollama",                       "B"),
    "langchain":            ("langchain",        "LangChain Agent Framework",                   "A"),
    "autogen":              ("autogen",          "AutoGen Multi-agent System",                  "A"),
    "crewai":               ("crewai",           "CrewAI Multi-agent Orchestration",            "A"),
    "atxp":                 ("custom",           "ATXP Funded Identity + Wallet + Tools",       "S"),
    "product-manager-skills":("linear",          "Senior PM Agent (30+ frameworks)",            "A"),
    "mcporter":             ("custom",           "MCP Server Discovery & Installation",         "B"),
    "groq":                 ("groq",             "Groq Ultra-fast LLM Inference",               "A"),
    "mistral":              ("custom",           "Mistral AI Model Integration",                "B"),
    "cohere":               ("custom",           "Cohere Language Model API",                   "B"),
    "huggingface":          ("custom",           "HuggingFace Model Hub Integration",           "B"),
    "llmcouncil-router":    ("custom",           "Multi-LLM Router & Council",                  "A"),
    "arya-model-router":    ("custom",           "Arya Intelligent LLM Router",                 "B"),
    "deepseek-reasoner-lite-agent":("custom",    "DeepSeek Reasoner Agent",                     "A"),
    "multi-agent-collab":   ("custom",           "Multi-agent Collaboration Network",           "A"),
    "agent-orchestrator":   ("custom",           "Agent Orchestration Framework",               "A"),
    "moa":                  ("custom",           "Mixture of Agents Ensemble",                  "A"),
    "replicate":            ("custom",           "Replicate AI Model Deployment",               "A"),
    "x-ai":                 ("custom",           "xAI Grok Model Integration",                  "B"),
    "model-guard":          ("custom",           "LLM Output Guard & Filter",                   "B"),
    "evoagentx":            ("custom",           "EvoAgentX Autonomous Agent Framework",        "A"),

    # === Database Engineering ===
    "postgres":             ("postgres",         "PostgreSQL via OpenClaw",                     "B"),
    "supabase":             ("supabase",         "Supabase via OpenClaw",                       "B"),
    "redis":                ("redis",            "Redis Cache via OpenClaw",                    "B"),
    "mongodb":              ("mongodb",          "MongoDB via OpenClaw",                        "B"),
    "sqlite":               ("custom",           "SQLite Database Automation",                  "C"),
    "elasticsearch-skill":  ("custom",           "Elasticsearch Search & Analytics",            "B"),
    "mongodb-atlas-admin":  ("mongodb",          "MongoDB Atlas Administration",                "B"),
    "duckdb-en":            ("custom",           "DuckDB Analytics Database",                   "B"),
    "sql-server-toolkit":   ("custom",           "SQL Server Automation Toolkit",               "B"),
    "db-readonly":          ("postgres",         "Read-only Database Query Agent",              "C"),

    # === Data Analytics ===
    "google-analytics-api": ("custom",           "Google Analytics API Integration",            "B"),
    "amplitude-automation": ("custom",           "Amplitude Analytics Automation",              "B"),
    "posthog":              ("custom",           "PostHog Product Analytics",                   "B"),
    "posthog-query":        ("custom",           "PostHog Query & Dashboard",                   "B"),
    "data-analyst":         ("custom",           "AI Data Analyst Agent",                       "A"),
    "data-enricher":        ("custom",           "Data Enrichment Pipeline",                    "B"),
    "csv-pipeline":         ("custom",           "CSV Data Pipeline Processor",                 "C"),
    "dune-analytics-api":   ("custom",           "Dune Analytics On-chain Data",                "B"),

    # === Blockchain & Web3 ===
    "defi-mcp":             ("alchemy",          "DeFi Protocol Integration",                   "B"),
    "alchemy":              ("alchemy",          "Alchemy Blockchain API",                      "B"),
    "solana":               ("solana",           "Solana Blockchain via OpenClaw",              "B"),
    "anchain":              ("alchemy",          "AnChain.AI AML Compliance",                   "B"),
    "solana-skills":        ("solana",           "Solana On-chain Actions",                     "B"),
    "solana-swaps":         ("solana",           "Solana DEX Swap Automation",                  "B"),
    "ethereum-history":     ("alchemy",          "Ethereum Transaction History",                "B"),
    "hedera-tx-builder":    ("custom",           "Hedera Transaction Builder",                  "B"),
    "zapper":               ("custom",           "Zapper DeFi Portfolio Tracker",               "B"),
    "lifi-crosschain":      ("custom",           "Li.Fi Cross-chain Bridge Agent",              "B"),
    "token-guard":          ("custom",           "Token Security & Guard Checks",               "B"),
    "coingecko-price":      ("coingecko",        "CoinGecko Live Price Feed",                   "B"),

    # === Finance & Payments ===
    "stripe":               ("stripe",           "Stripe Payments via OpenClaw",                "B"),
    "plaid":                ("plaid",            "Bank Data Integration via OpenClaw",          "B"),
    "quickbooks":           ("quickbooks",       "QuickBooks Accounting Automation",            "B"),
    "quickbooks-online":    ("quickbooks",       "QuickBooks Online Automation",                "B"),
    "moonpay":              ("custom",           "MoonPay Crypto On-ramp",                      "B"),
    "smartbill-invoicing":  ("custom",           "SmartBill Invoice Automation",                "B"),
    "financial-reconciler": ("custom",           "AI Financial Reconciliation Agent",           "B"),
    "starling-bank":        ("custom",           "Starling Bank API Integration",               "B"),
    "revolut-business":     ("custom",           "Revolut Business Banking",                    "B"),
    "vibetrader":           ("custom",           "Vibe Trader Financial Analysis",              "B"),

    # === Smart Home & IoT ===
    "home-assistant":       ("home-assistant",   "Home Assistant Automation",                   "B"),
    "philips-hue":          ("home-assistant",   "Philips Hue Smart Lighting",                  "C"),
    "homekit":              ("apple",            "Apple HomeKit Automation",                    "C"),
    "nest-sdm":             ("home-assistant",   "Google Nest Smart Device Management",         "B"),
    "govee-lights":         ("home-assistant",   "Govee Smart Lighting Control",                "C"),
    "homey":                ("home-assistant",   "Homey Smart Home Hub",                        "B"),
    "wiz-light-control":    ("home-assistant",   "WiZ Smart Light Control",                     "C"),
    "google-home":          ("home-assistant",   "Google Home Device Control",                  "B"),
    "tasmota-skill":        ("home-assistant",   "Tasmota IoT Firmware Control",                "B"),
    "node-red-manager":     ("home-assistant",   "Node-RED Flow Automation",                    "B"),
    "ipcam":                ("home-assistant",   "IP Camera Monitoring & Control",              "C"),
    "dirigera-control":     ("home-assistant",   "IKEA Dirigera Smart Home Hub",                "B"),

    # === Self-hosted & Workflow Automation ===
    "n8n":                  ("n8n",              "n8n Workflow Automation",                     "B"),
    "activepieces":         ("n8n",              "Activepieces Open-source Automation",         "B"),
    "windmill":             ("n8n",              "Windmill Workflow Engine",                    "B"),
    "reef-n8n-automation":  ("n8n",              "n8n Automation via Reef",                     "B"),
    "workflow-engine":      ("n8n",              "Generic Workflow Engine",                     "B"),
    "casual-cron":          ("automation",       "Casual Cron Job Manager",                     "C"),

    # === Project Management ===
    "linear":               ("linear",           "Linear Issue Tracking",                       "B"),
    "jira":                 ("linear",           "Jira Project Management",                     "B"),
    "asana":                ("asana",            "Asana Task Management",                       "B"),
    "clickup":              ("clickup",          "ClickUp Project Management",                  "B"),
    "clickup-skill":        ("clickup",          "ClickUp Skill Tasks & Docs",                  "B"),
    "trello":               ("custom",           "Trello Board Automation",                     "B"),
    "wrike":                ("custom",           "Wrike Work Management",                       "B"),
    "vikunja-kanban":       ("custom",           "Vikunja Open-source Kanban",                  "B"),
    "atlassian-mcp":        ("linear",           "Atlassian Suite MCP Integration",             "B"),

    # === E-Commerce ===
    "amazon-shopper":       ("amazon-s3",        "Amazon Product Search & Shopping",            "B"),
    "amazon-reviews-api-skill":("amazon-s3",     "Amazon Reviews API",                          "B"),
    "shopify-bulk-upload":  ("custom",           "Shopify Bulk Product Upload",                 "B"),
    "clawpify":             ("custom",           "ClawPify Shopify Automation",                 "B"),
    "scout-commerce":       ("custom",           "E-commerce Intelligence Scout",               "B"),
    "amazon-competitor-analyzer":("amazon-s3",   "Amazon Competitor Analysis Agent",            "A"),
    "priceforagent":        ("custom",           "Price Comparison Agent",                      "B"),
    "tradekix":             ("custom",           "TradeKix E-commerce Agent",                   "B"),

    # === Marketing ===
    "campaign-orchestrator":("custom",           "AI Marketing Campaign Orchestrator",          "A"),
    "content-creator":      ("custom",           "AI Content Creator Agent",                    "B"),
    "blog-writer":          ("custom",           "AI Blog Post Writer",                         "B"),
    "seo-ranker":           ("custom",           "SEO Content Ranking Agent",                   "B"),
    "marketing-strategy-pmm":("custom",          "Product Marketing Strategy Agent",            "A"),
    "ad-ready-pro":         ("custom",           "Ad-Ready Pro Marketing Agent",                "B"),
    "meta-ads-report":      ("custom",           "Meta Ads Performance Reporter",               "B"),

    # === Apple Ecosystem ===
    "apple-health-skill":   ("apple",            "Apple Health Data Integration",               "B"),
    "apple-notes":          ("apple",            "Apple Notes Automation",                      "B"),
    "apple-photos":         ("apple",            "Apple Photos Library Automation",             "B"),
    "apple-contacts":       ("apple",            "Apple Contacts Automation",                   "B"),
    "shortcuts":            ("apple",            "Apple Shortcuts Automation",                  "B"),
    "callmac":              ("apple",            "macOS Phone Call Automation",                 "B"),

    # === iOS/macOS Development ===
    "xcode":                ("apple",            "Xcode iOS/macOS Development",                 "A"),
    "ios-simulator":        ("apple",            "iOS Simulator Automation",                    "A"),
    "swift-concurrency-expert":("apple",         "Swift Concurrency Expert Agent",              "A"),
    "macos-spm-app-packaging":("apple",          "macOS Swift Package Bundler",                 "B"),
    "instruments-profiling":("apple",            "Xcode Instruments Profiling",                 "B"),
    "app-store-optimization":("apple",           "App Store Optimization Agent",                "A"),

    # === Moltbook / Social Platform ===
    "moltbook":             ("custom",           "Moltbook Social Platform Integration",        "B"),
    "moltbook-interact":    ("custom",           "Moltbook Interaction API",                    "B"),
    "agentchat":            ("slack",            "AgentChat Multi-platform Messaging",          "B"),
    "agentgram-openclaw":   ("custom",           "AgentGram Social Media Engine",               "B"),
    "clankedin":            ("linkedin",         "Clankedin LinkedIn Automation",               "B"),
    "molt-trust":           ("custom",           "Moltbook Trust Verification",                 "B"),

    # === Health & Fitness ===
    "fitbit":               ("custom",           "Fitbit Health Data Integration",              "B"),
    "fitbit-analytics":     ("custom",           "Fitbit Analytics & Trends",                   "B"),
    "garmin-health":        ("custom",           "Garmin Health Data Sync",                     "B"),
    "oura-analytics":       ("custom",           "Oura Ring Health Analytics",                  "B"),
    "intervals-icu":        ("custom",           "Intervals.icu Training Load Analysis",        "B"),
    "hevy":                 ("custom",           "Hevy Workout Tracker Integration",            "B"),

    # === Personal Development ===
    "adaptive-learning-agents":("custom",        "Adaptive Learning AI Agent",                  "B"),
    "adversarial-coach":    ("custom",           "Adversarial Coaching Agent",                  "B"),
    "agent-reflect":        ("custom",           "Self-reflection & Growth Journal",            "B"),
    "crucial-conversations-coach":("custom",     "Crucial Conversations Coach",                 "B"),

    # === Security ===
    "1password":            ("1password",        "1Password Secrets & Credentials",             "B"),
    "bitwarden":            ("bitwarden",        "Bitwarden Password Manager",                  "B"),
    "ggshield-scanner":     ("custom",           "GitGuardian Secret Scanner",                  "A"),
    "clawdstrike":          ("custom",           "ClawdStrike Security Audit",                  "B"),
    "sovereign-api-hardener":("custom",          "API Security Hardening Agent",                "A"),
    "agent-hardening":      ("custom",           "Agent Hardening & Security Audit",            "B"),
    "adguard":              ("custom",           "AdGuard DNS & Ad Blocking",                   "B"),
    "credential-manager":   ("1password",        "Credential Manager Automation",               "B"),

    # === Travel ===
    "amadeus-flights":      ("custom",           "Amadeus Flight Search & Booking",             "A"),
    "google-flights":       ("custom",           "Google Flights Price Monitor",                "B"),
    "aviationstack-flight-tracker":("custom",    "Aviation Stack Flight Tracker",               "B"),
    "ryanair-fare-finder":  ("custom",           "Ryanair Fare Finder Agent",                   "B"),
    "flight-pricer":        ("custom",           "Flight Price Comparison Agent",               "B"),
    "hotel-pricer":         ("custom",           "Hotel Price Comparison Agent",                "B"),

    # === Other Notable Skills ===
    "figma":                ("webflow",          "Figma Design Automation & Export",            "B"),
    "airtable-automation":  ("airtable",         "Airtable Base Automation",                    "B"),
    "bamboohr-automation":  ("custom",           "BambooHR HR Automation",                      "B"),
    "erpnext-frappe":       ("custom",           "ERPNext/Frappe Business Suite",               "B"),
    "suno-music":           ("custom",           "Suno AI Music Generation",                    "A"),
    "spotify":              ("spotify",          "Spotify Music Control",                       "C"),
    "youtube":              ("custom",           "YouTube Data & Automation",                   "B"),
    "plex-ctl":             ("custom",           "Plex Media Server Control",                   "B"),
    "multi-agent-orchestration":("custom",       "Multi-agent Orchestration System",            "A"),
    "agent-council":        ("custom",           "Agent Council Decision Framework",            "A"),
    "agent-synthesis":      ("custom",           "Agent Synthesis & Summarization",             "B"),
    "smart-context":        ("custom",           "Smart Context Management Agent",              "B"),
    "metacognition":        ("custom",           "Metacognitive Reasoning Agent",               "A"),
}

def _detect_openclaw_skills() -> list[str]:
    """Detect installed OpenClaw skills from ~/.openclaw/skills/ directory.

    OpenClaw stores skills as subdirectories — the folder name IS the skill name.
    Falls back to ~/.openclaw/config.json for explicitly enabled skills.
    """
    skill_names: set[str] = set()

    # 1. Scan ~/.openclaw/skills/ for installed skill directories
    skills_dir = Path.home() / ".openclaw" / "skills"
    if skills_dir.is_dir():
        for entry in skills_dir.iterdir():
            if entry.is_dir() and not entry.name.startswith("."):
                skill_names.add(entry.name.lower())

    # 2. Scan ~/Library/Application Support/OpenClaw/skills (macOS app store variant)
    macos_skills = Path.home() / "Library" / "Application Support" / "OpenClaw" / "skills"
    if macos_skills.is_dir():
        for entry in macos_skills.iterdir():
            if entry.is_dir() and not entry.name.startswith("."):
                skill_names.add(entry.name.lower())

    # 3. Read openclaw config for explicitly listed skills/mcpServers
    for cfg_path in [
        Path.home() / ".openclaw" / "config.json",
        Path.home() / ".openclaw" / "settings.json",
        Path.home() / ".openclaw" / "mcp.json",
    ]:
        if cfg_path.exists():
            try:
                cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
                for key in ("skills", "enabledSkills", "installedSkills"):
                    for s in cfg.get(key, []):
                        if isinstance(s, str):
                            skill_names.add(s.lower())
                        elif isinstance(s, dict):
                            name = s.get("name") or s.get("id") or ""
                            if name:
                                skill_names.add(name.lower())
                for srv in cfg.get("mcpServers", {}).keys():
                    skill_names.add(srv.lower())
            except Exception:
                pass

    return sorted(skill_names)


def _detect_local_capabilities() -> dict:
    """Read ~/.claude.json and ~/.openclaw/ to extract all installed capabilities."""
    # ── Claude Code capabilities ───────────────────────────────────────────
    claude_json = Path.home() / ".claude.json"
    mcp_names: set[str] = set()
    plugin_names: list[str] = []
    skill_ids: list[str] = []

    if claude_json.exists():
        try:
            data = json.loads(claude_json.read_text(encoding="utf-8"))
            # 1. MCP servers — global + all projects (deduplicated)
            mcp_names = set(data.get("mcpServers", {}).keys())
            for proj in data.get("projects", {}).values():
                mcp_names.update(proj.get("mcpServers", {}).keys())
            mcp_names.discard("poolclaw")  # don't self-report

            # 2. Claude plugins (Harbor marketplace)
            plugin_names = [
                p.get("plugin", "") for p in
                data.get("cachedGrowthBookFeatures", {}).get("tengu_harbor_ledger", [])
                if p.get("plugin")
            ]

            # 3. Claude Code skills (skillUsage keys = skill IDs)
            skill_ids = list(data.get("skillUsage", {}).keys())
        except Exception:
            pass

    # ── OpenClaw capabilities ──────────────────────────────────────────────
    openclaw_skills = _detect_openclaw_skills()

    return {
        "mcps": sorted(mcp_names),
        "plugins": plugin_names,
        "skills": skill_ids,
        "openclaw_skills": openclaw_skills,
    }


def _build_specialties(caps: dict) -> list[str]:
    """Map detected capabilities to deduplicated specialty labels."""
    seen: set[str] = set()
    result: list[str] = []

    def _add(label: str) -> None:
        if label not in seen:
            seen.add(label)
            result.append(label)

    for mcp_name in caps["mcps"]:
        label = _MCP_SPECIALTY.get(mcp_name)
        if not label:
            for key, val in _MCP_SPECIALTY.items():
                if mcp_name.startswith(key) or key in mcp_name:
                    label = val
                    break
        if label:
            _add(label)

    for plugin in caps["plugins"]:
        label = _PLUGIN_SPECIALTY.get(plugin)
        if label:
            _add(label)

    for skill_id in caps["skills"]:
        for prefix, label in _SKILL_SPECIALTY.items():
            if skill_id.startswith(prefix):
                _add(label)
                break

    for oc_skill in caps.get("openclaw_skills", []):
        # exact match first
        label = _OPENCLAW_SKILL_SPECIALTY.get(oc_skill)
        if not label:
            # prefix / substring match
            for key, val in _OPENCLAW_SKILL_SPECIALTY.items():
                if oc_skill.startswith(key) or key in oc_skill:
                    label = val
                    break
        if label:
            _add(label)

    return result


# Capability name → (service_id, skill_name, tier) for agent-skills registration.
# service_id must match a known service or use a descriptive slug.
# UNIQUE(identity_id, service) in DB — first call wins; subsequent calls update.
_CAPABILITY_SKILLS: dict[str, tuple[str, str, str]] = {
    # ── Design & 3D ──────────────────────────────────────────────────────────
    "blender":                   ("blender",    "3D Modeling & Scene Automation",      "A"),
    "figma":                     ("figma",      "UI/UX Design & Prototyping",           "A"),
    "unity":                     ("unity",      "Game Development & 3D Scenes",         "B"),
    "godot":                     ("godot",      "Open-source Game Development",          "B"),
    "unreal":                    ("unreal",     "Unreal Engine Development",             "B"),
    "maya":                      ("maya",       "3D Animation & VFX",                   "B"),
    "stable-diffusion":          ("stable-diffusion", "AI Image Generation & Inpainting","A"),
    "midjourney":                ("midjourney", "AI Concept Art & Visual Design",        "A"),
    "dall-e":                    ("dall-e",     "OpenAI Image Generation",               "B"),
    "runway":                    ("runway",     "AI Video Generation & Editing",         "A"),
    "eleven-labs":               ("eleven-labs","AI Voice Cloning & TTS",               "A"),
    "assemblyai":                ("assemblyai", "Speech-to-text & Audio Intelligence",   "B"),
    "deepgram":                  ("deepgram",   "Real-time Transcription",               "B"),
    # ── Software Development ─────────────────────────────────────────────────
    "github":                    ("github",     "Code Review & Repository Management",  "A"),
    "gitlab":                    ("gitlab",     "CI/CD & Repository Management",         "B"),
    "bitbucket":                 ("bitbucket",  "Atlassian Repository Management",       "C"),
    "sourcegraph":               ("sourcegraph","Code Search & Codebase Navigation",     "B"),
    # ── Web Automation & Research ────────────────────────────────────────────
    "puppeteer":                 ("puppeteer",  "Browser Automation & Scraping",         "B"),
    "playwright":                ("playwright", "Cross-browser Test Automation",         "A"),
    "selenium":                  ("selenium",   "Browser Automation Testing",            "B"),
    "firecrawl":                 ("firecrawl",  "Web Scraping & Deep Research",          "A"),
    "tavily":                    ("tavily",     "AI-powered Web Search",                 "A"),
    "exa":                       ("exa",        "Semantic Web Search & Discovery",       "A"),
    "brave-search":              ("brave-search","Privacy-first Web Search",             "B"),
    "apify":                     ("apify",      "Web Scraping & Actor Automation",       "A"),
    "perplexity":                ("perplexity", "AI Research & Answer Engine",           "B"),
    # ── Databases ───────────────────────────────────────────────────────────
    "postgres":                  ("postgres",   "Database Queries & Migrations",         "B"),
    "supabase":                  ("supabase",   "Supabase Database & Auth",              "B"),
    "sqlite":                    ("sqlite",     "Embedded Database Automation",          "C"),
    "mysql":                     ("mysql",      "MySQL Database Engineering",            "B"),
    "mongodb":                   ("mongodb",    "NoSQL Document Database",               "B"),
    "redis":                     ("redis",      "In-memory Caching & Pub/Sub",           "B"),
    "neo4j":                     ("neo4j",      "Graph Database Queries",                "B"),
    "elasticsearch":             ("elasticsearch","Full-text Search Engineering",        "B"),
    "qdrant":                    ("qdrant",     "Vector Search & Similarity Queries",    "A"),
    "pinecone":                  ("pinecone",   "Vector Database & RAG Pipelines",       "A"),
    "weaviate":                  ("weaviate",   "Vector Search & Generative AI",         "A"),
    "chroma":                    ("chroma",     "Local Vector Database for AI",          "B"),
    "influxdb":                  ("influxdb",   "Time-series Data & IoT Analytics",      "B"),
    "dynamodb":                  ("dynamodb",   "AWS DynamoDB Engineering",              "B"),
    "firestore":                 ("firestore",  "Firebase Realtime Database",            "B"),
    # ── DevOps & Cloud ───────────────────────────────────────────────────────
    "vercel":                    ("vercel",     "App Deployment & Edge Functions",       "A"),
    "netlify":                   ("netlify",    "JAMstack Deployment & Hosting",         "B"),
    "aws":                       ("amazon-s3",  "AWS Cloud Infrastructure",              "A"),
    "cloudflare":                ("cloudflare", "Edge Computing & CDN Management",       "A"),
    "docker":                    ("docker",     "Container Build & Orchestration",       "A"),
    "kubernetes":                ("kubernetes", "Container Orchestration at Scale",      "A"),
    "terraform":                 ("terraform",  "Infrastructure as Code",                "A"),
    "ansible":                   ("ansible",    "Configuration Management & IaC",        "B"),
    "jenkins":                   ("jenkins",    "CI/CD Pipeline Automation",             "B"),
    "fly":                       ("fly",        "Fly.io App Deployment",                 "B"),
    "railway":                   ("railway",    "Railway Deployment Platform",           "B"),
    "firebase":                  ("firebase",   "Firebase App Platform",                 "B"),
    "gcp":                       ("google-cloud-storage","Google Cloud Platform",        "B"),
    "azure":                     ("azure",      "Microsoft Azure Cloud",                 "B"),
    "digitalocean":              ("digitalocean","DigitalOcean Cloud Infrastructure",    "B"),
    # ── Observability ────────────────────────────────────────────────────────
    "datadog":                   ("datadog",    "Observability & APM Alerting",          "B"),
    "pagerduty":                 ("pagerduty",  "Incident Response & On-call",           "B"),
    "sentry":                    ("sentry",     "Error Tracking & Performance",          "B"),
    "grafana":                   ("grafana",    "Metrics Dashboards & Alerts",           "B"),
    "newrelic":                  ("newrelic",   "Full-stack Observability",              "B"),
    # ── Project Management ───────────────────────────────────────────────────
    "linear":                    ("linear",     "Issue Tracking & Product Sprints",      "B"),
    "jira":                      ("jira",       "Atlassian Project Management",          "B"),
    "asana":                     ("asana",      "Task & Project Coordination",           "B"),
    "clickup":                   ("clickup",    "All-in-one Project Management",         "C"),
    "notion":                    ("notion",     "Knowledge Base & Docs Automation",      "B"),
    "trello":                    ("trello",     "Kanban Board Automation",               "C"),
    "shortcut":                  ("shortcut",   "Engineering Sprint Management",          "B"),
    "miro":                      ("miro",       "Visual Whiteboard Collaboration",       "B"),
    "confluence":                ("confluence", "Team Knowledge Management",             "B"),
    # ── Communication & Messaging ────────────────────────────────────────────
    "slack":                     ("slack",      "Slack Messaging & Workflow Automation", "B"),
    "discord":                   ("discord",    "Discord Bot & Server Management",       "B"),
    "telegram":                  ("telegram",   "Telegram Bot Development",              "B"),
    "microsoft-graph":           ("microsoft-graph","Microsoft 365 & Teams Automation", "B"),
    "gmail":                     ("gmail",      "Gmail Automation & Filtering",          "B"),
    "sendgrid":                  ("sendgrid",   "Transactional Email Delivery",          "B"),
    "mailchimp":                 ("mailchimp",  "Email Campaign Automation",             "C"),
    "twilio":                    ("twilio",     "SMS, Voice & Communication APIs",       "B"),
    "intercom":                  ("intercom",   "Customer Messaging & Support",          "B"),
    "google-calendar":           ("google-calendar","Calendar Event Automation",         "C"),
    # ── CRM & Sales ─────────────────────────────────────────────────────────
    "salesforce":                ("salesforce", "Salesforce CRM Automation",             "B"),
    "hubspot":                   ("hubspot",    "HubSpot CRM & Marketing",               "B"),
    "pipedrive":                 ("pipedrive",  "Sales Pipeline Management",             "C"),
    "zendesk":                   ("zendesk",    "Customer Support Ticketing",            "B"),
    "freshdesk":                 ("freshdesk",  "Customer Support Automation",           "C"),
    # ── E-commerce & Payments ────────────────────────────────────────────────
    "shopify":                   ("shopify",    "Shopify Store Automation",              "B"),
    "stripe":                    ("stripe",     "Payment Processing & Billing",          "B"),
    "paypal":                    ("paypal",     "PayPal Payment Integration",            "C"),
    "plaid":                     ("plaid",      "Fintech & Bank Data Integration",       "B"),
    "quickbooks":                ("quickbooks", "Accounting & Invoice Automation",       "C"),
    "xero":                      ("xero",       "Accounting & Finance Automation",       "C"),
    # ── CMS & Content ───────────────────────────────────────────────────────
    "wordpress":                 ("wordpress",  "WordPress CMS Management",              "C"),
    "contentful":                ("contentful", "Headless CMS Content Automation",       "B"),
    "sanity":                    ("sanity",     "Structured Content & GROQ Queries",     "B"),
    "webflow":                   ("webflow",    "No-code Web Design & CMS",              "C"),
    "airtable":                  ("airtable",   "Structured Data & Base Automation",     "B"),
    # ── Analytics & Data ─────────────────────────────────────────────────────
    "segment":                   ("segment",    "Customer Data Platform Integration",    "B"),
    "mixpanel":                  ("mixpanel",   "Product Analytics & Funnel Analysis",   "B"),
    "amplitude":                 ("amplitude",  "Product Intelligence & A/B Testing",   "B"),
    "posthog":                   ("posthog",    "Open-source Product Analytics",         "B"),
    # ── Workflow Automation ───────────────────────────────────────────────────
    "zapier":                    ("zapier",     "No-code Workflow Automation",           "B"),
    "make":                      ("make",       "Visual Workflow Automation (Make)",     "B"),
    "n8n":                       ("n8n",        "Self-hosted Workflow Automation",       "B"),
    "temporal":                  ("temporal",   "Durable Workflow Orchestration",        "A"),
    "airflow":                   ("airflow",    "Data Pipeline Orchestration",           "A"),
    "prefect":                   ("prefect",    "Modern Data Pipeline Orchestration",    "A"),
    # ── AI & ML ──────────────────────────────────────────────────────────────
    "openai":                    ("openai",     "OpenAI API Integration & Fine-tuning",  "A"),
    "anthropic":                 ("anthropic",  "Claude API & Agent Development",        "A"),
    "huggingface":               ("huggingface","Model Hub & Inference API",             "A"),
    "langchain":                 ("langchain",  "LangChain AI Orchestration",            "A"),
    "llamaindex":                ("llamaindex", "LlamaIndex RAG & Data Agents",          "A"),
    "replicate":                 ("replicate",  "AI Model Inference & Deployment",       "B"),
    "groq":                      ("groq",       "Ultra-fast LLM Inference",              "B"),
    "ollama":                    ("ollama",     "Local LLM Serving & Management",        "B"),
    "mistral":                   ("mistral",    "Mistral AI Models & API",               "B"),
    "cohere":                    ("cohere",     "Cohere Embeddings & Rerank",            "B"),
    "together-ai":               ("together-ai","Open-source Model Inference",           "B"),
    "dspy":                      ("dspy",       "DSPy LM Program Optimization",          "A"),
    "autogen":                   ("autogen",    "Multi-agent Conversation Framework",    "A"),
    "crewai":                    ("crewai",     "Multi-agent Role-play Orchestration",   "A"),
    "mlflow":                    ("mlflow",     "ML Experiment Tracking & Registry",     "B"),
    "wandb":                     ("wandb",      "ML Experiment Tracking & Sweep",        "B"),
    "modal":                     ("modal",      "Serverless AI Model Deployment",        "A"),
    "memory":                    ("memory",     "Persistent Agent Memory Management",    "B"),
    # ── Blockchain & Web3 ────────────────────────────────────────────────────
    "alchemy":                   ("alchemy",    "Blockchain Node API & Webhooks",        "B"),
    "moralis":                   ("moralis",    "Web3 Data & dApp Development",          "B"),
    "solana":                    ("solana",     "Solana Smart Contract Development",     "B"),
    # ── Files & Storage ──────────────────────────────────────────────────────
    "filesystem":                ("filesystem", "Local File System Automation",          "C"),
    "amazon-s3":                 ("amazon-s3",  "S3 Object Storage Management",          "B"),
    "google-cloud-storage":      ("google-cloud-storage","GCS Bucket Management",        "B"),
    "google-drive":              ("google-drive","Google Drive File Automation",         "B"),
    # ── OpenClaw / PoolClaw ecosystem ────────────────────────────────────────
    "openclaw":                  ("poolclaw",   "OpenClaw Autonomous Agent Operation",   "S"),
    # ── Claude Code skills ───────────────────────────────────────────────────
    "frontend-design":           ("figma",      "Frontend UI Design & Implementation",  "A"),
    "frontend-design:frontend-design": ("figma","Frontend UI Design & Implementation",  "A"),
    "firecrawl:firecrawl-cli":   ("firecrawl",  "AI-powered Web Research & Scraping",   "A"),
    "figma:implement-design":    ("figma",      "Figma-to-code Implementation",          "A"),
    "figma:code-connect-components": ("figma",  "Figma Code Connect Mapping",           "B"),
    "figma:create-design-system-rules": ("figma","Design System Rules Generation",      "B"),
    "vercel:deploy":             ("vercel",     "App Deployment & CI/CD",                "A"),
    "vercel:logs":               ("vercel",     "Vercel Deployment Log Analysis",        "B"),
    "vercel:setup":              ("vercel",     "Vercel Project Configuration",          "B"),
    "stripe:explain-error":      ("stripe",     "Stripe Error Diagnosis & Fix",          "B"),
    "stripe:test-cards":         ("stripe",     "Stripe Payment Testing",                "C"),
    "stripe:stripe-best-practices": ("stripe",  "Stripe Integration Best Practices",    "B"),
    "claude-api":                ("anthropic",  "Claude API & Agent SDK Development",   "A"),
    "agent-sdk-dev:new-sdk-app": ("anthropic",  "Claude Agent SDK App Development",     "A"),
    "anthropic-skills:pptx":     ("office",     "PowerPoint Presentation Automation",   "B"),
    "anthropic-skills:pdf":      ("pdf",        "PDF Processing & Generation",           "B"),
    "anthropic-skills:xlsx":     ("spreadsheet","Spreadsheet Automation & Analysis",    "B"),
    "anthropic-skills:docx":     ("office",     "Word Document Creation & Editing",     "B"),
    "anthropic-skills:schedule": ("automation", "Recurring Task Scheduling",             "B"),
    "pr-review-toolkit:review-pr": ("github",   "Pull Request Review & Analysis",       "A"),
    "feature-dev:feature-dev":   ("github",     "Feature Planning & Development",       "A"),
    "code-simplifier:code-simplifier": ("github","Code Simplification & Refactoring",   "A"),
    "simplify":                  ("github",     "Code Quality Improvement",              "B"),
    "loop":                      ("automation", "Interval-based Task Automation",        "C"),
    "update-config":             ("automation", "Claude Code Settings Configuration",    "C"),
}


async def _auto_sync_profile() -> None:
    """On first authenticated call: detect local capabilities, update specialty
    and register each capability as a searchable agent-skill."""
    global _profile_synced
    if _profile_synced:
        return
    _profile_synced = True

    tok = _active_token()
    if not tok:
        return

    caps = _detect_local_capabilities()
    specialties = _build_specialties(caps)
    url = _api_url(SERVER_URL)
    headers = {"X-Agent-Token": tok}

    # Build the list of skills to register (Claude + OpenClaw)
    all_cap_names = (
        list(caps["mcps"])
        + list(caps["plugins"])
        + list(caps["skills"])
    )
    skills_to_register: list[tuple[str, str, str]] = []
    seen_services: set[str] = set()

    for cap in all_cap_names:
        entry = _CAPABILITY_SKILLS.get(cap)
        if entry and entry[0] not in seen_services:
            skills_to_register.append(entry)
            seen_services.add(entry[0])

    # OpenClaw skills (separate dict, may overlap services — keep first)
    for oc_skill in caps.get("openclaw_skills", []):
        entry = _OPENCLAW_CAPABILITY_SKILLS.get(oc_skill)
        if not entry:
            # prefix match
            for key, val in _OPENCLAW_CAPABILITY_SKILLS.items():
                if oc_skill.startswith(key) or key in oc_skill:
                    entry = val
                    break
        if entry and entry[0] not in seen_services:
            skills_to_register.append(entry)
            seen_services.add(entry[0])

    try:
        async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
            # 1. Update profile specialty (comma-separated human-readable list)
            if specialties:
                await client.patch(
                    f"{url}/auth/me",
                    json={"specialty": ", ".join(specialties)},
                    headers=headers,
                )
                log.info("Auto-synced specialty: %s", ", ".join(specialties))

            # 2. Register each capability as an agent-skill (idempotent — UNIQUE guard in DB)
            for service, skill_name, tier in skills_to_register:
                resp = await client.post(
                    f"{url}/agent-skills",
                    json={"service": service, "skill_name": skill_name, "tier": tier},
                    headers=headers,
                )
                if resp.is_success:
                    log.info("Registered skill: %s / %s", service, skill_name)
    except Exception:
        pass  # non-blocking — never fail a tool call because of this


def _api_url(server_url: str) -> str:
    """Resolve HTTP API base: port 3000 (Rust WS) → port 8000 (Python API).

    NOTE: This remapping only applies to local dev. Production URLs have no
    explicit port and are returned as-is.
    """
    parsed = urlparse(server_url)
    if parsed.port == 3000:
        log.warning("Remapping port 3000 → 8000 for local dev (%s)", server_url)
        return f"{parsed.scheme}://{parsed.hostname}:8000"
    return server_url.rstrip("/")


def _active_token() -> str:
    """Return the active agent token: session login takes priority over env var."""
    return _session.get("agent_token", "") or _ENV_TOKEN


def _headers() -> dict[str, str]:
    """Build auth headers using the active agent token.
    Also fires the one-time profile sync on the first authenticated call.
    """
    tok = _active_token()
    if tok and not _profile_synced:
        asyncio.ensure_future(_auto_sync_profile())
    return {"X-Agent-Token": tok} if tok else {}


def _safe_error(resp: httpx.Response) -> str:
    """Extract a clean error message without leaking raw backend internals."""
    try:
        body = resp.json()
        return body.get("detail") or body.get("message") or body.get("error") or f"Error {resp.status_code}"
    except Exception:
        return f"Error {resp.status_code}"


def _current_api_url() -> str:
    """Return the API URL using session server_url or the global default."""
    return _api_url(_session.get("server_url", SERVER_URL))


def _current_pool_id(pool_id: str) -> str:
    """Resolve pool_id: use provided value or fall back to connected pool."""
    return pool_id or _session.get("pool_id", "")


def _pool_url(pool_id: str) -> str:
    """Return the frontend URL for a given pool UUID."""
    return f"{FRONTEND_URL.rstrip('/')}/#/pool/{pool_id}"


def _page_url(page: str = "") -> str:
    """Return the frontend URL for a site page (e.g. 'marketplace', 'jobs', 'leaderboard')."""
    base = FRONTEND_URL.rstrip("/")
    return f"{base}/#{('/' + page) if page else ''}"


def _profile_url(username: str) -> str:
    """Return the frontend URL for a user's public profile."""
    return f"{FRONTEND_URL.rstrip('/')}/#/profile/{username}"


async def _ws_listener(ws_url: str, queue: asyncio.Queue) -> None:
    """Background task: maintain WS connection and enqueue all incoming events.

    Receives `queue` as an explicit argument to avoid reading a stale module global
    after reconnects.
    """
    try:
        async with websockets.connect(
            ws_url,
            ping_interval=30,
            open_timeout=10,
        ) as ws:
            _ws_handle[:] = [ws]
            async for raw in ws:
                try:
                    await queue.put(json.loads(raw))
                except Exception:
                    pass
    except asyncio.CancelledError:
        pass  # clean shutdown on reconnect
    except Exception as exc:
        await queue.put({"type": "pool:error", "message": "WebSocket disconnected"})
        log.debug("WS listener error: %s", exc)
    finally:
        _ws_handle.clear()


# ── Pool Management ──────────────────────────────────────────────────────────

@mcp.tool()
async def create_pool(
    name: str,
    problem: str,
    max_agents: int = 4,
    mode: str = "autonomous",
    pool_type: str = "",
    methodology_id: str = "",
    category: str = "",
    visibility: str = "public",
    pool_nature: str = "running",
    parent_id: str = "",
    tier: str = "",
    ip_agreement_type: str = "contribution_weighted",
    ip_license: str = "MIT",
    rules: str = "",
    agent_token: str = "",
) -> str:
    """Create a new PoolClaw pool.

    Pools are collaborative workspaces where AI agents and humans team up to work
    on projects together. The pool automatically handles task types, methodology,
    and workflow structure during execution.

    SIMPLE WORKFLOW:
    1. Just tell Claude what you want to build/solve and how many agents (2-10)
    2. Call create_pool() with the name and problem description
    3. Call connect_pool() to join and start working

    That's it. Everything else (task types, methodology, category) is determined
    automatically during the pool execution. No need to specify them upfront.

    max_agents counts total participants: if you set max_agents=3, that includes
    you + 2 other agents (total 3 people in the pool).

    Args:
        name: Short title for the pool (e.g. "Build AI recipe app").
        problem: What you're trying to accomplish (min 10 chars).
        max_agents: Total people in pool (2-10, default 4). Example: max_agents=2 means you + 1 other person.
        mode: Work mode — "autonomous" (default, planning→fusion→dispatch cycle), "project" (fixed rounds), or "solo".
        visibility: "public" (visible in marketplace) or "private" (invite-only). Default: public.
        pool_nature: "running" (ongoing project, default) or "solvable" (has a clear done state).
        parent_id: UUID of parent pool (for sub-pools). Leave empty for top-level.
        tier: Minimum AI model tier needed to join. Empty = open to all.
              Values: "S" (GPT-4o, Claude Opus), "A", "B", "C", "D".
        ip_agreement_type: How IP/revenue is shared (default: contribution_weighted).
              Values: contribution_weighted, shared_equal, creator_owns, open_source, revenue_share, custom.
        ip_license: License for output (default: MIT).
              Values: MIT, Apache-2.0, GPL-3.0, CC-BY-4.0, CC0-1.0, Proprietary, Custom.
        rules: Comma-separated pool rules (optional).
        agent_token: Auth token. Defaults to POOLCLAW_TOKEN env var.

        DO NOT SPECIFY: pool_type, methodology_id, category. These are auto-detected during work.
    """
    token = agent_token or _active_token()
    if not token:
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _api_url(SERVER_URL)
    hdrs = {"Content-Type": "application/json", "X-Agent-Token": token}

    payload: dict[str, Any] = {
        "name": name,
        "problem": problem,
        "max_agents": max_agents,
        "mode": mode,
        "visibility": visibility,
        "pool_nature": pool_nature,
        "ip_agreement_type": ip_agreement_type,
        "ip_license": ip_license,
    }
    if pool_type:
        payload["pool_type"] = pool_type
    if methodology_id:
        payload["methodology_id"] = methodology_id
    if category:
        payload["category"] = category
    if parent_id:
        payload["parent_id"] = parent_id
    if tier:
        payload["tier"] = tier
    if rules:
        payload["rules"] = [r.strip() for r in rules.split(",") if r.strip()]

    async with httpx.AsyncClient(timeout=15) as http:
        resp = await http.post(f"{url}/pools", json=payload, headers=hdrs)

    if resp.status_code not in (200, 201):
        return f"Pool creation failed: {_safe_error(resp)}"

    data = resp.json()
    pool_id = data.get("pool_id") or data.get("id") or (data.get("pool") or {}).get("id", "")
    methodology_info = data.get("methodology") or {}
    meth_name = methodology_info.get("name", methodology_id or "auto")
    meth_rounds = methodology_info.get("rounds", "?")

    lines = [
        "Pool created!",
        f"pool_id: {pool_id}",
        f"name: {name}",
        f"nature: {pool_nature} | visibility: {visibility}",
        f"methodology: {meth_name} ({meth_rounds} rounds)",
        f"IP: {ip_agreement_type} ({ip_license})",
        f"agents needed: {max_agents}",
        f"tier requirement: {tier}" if tier else "tier: open to all",
    ]
    if parent_id:
        lines.append(f"parent pool: {parent_id}")
    pool_url = _pool_url(pool_id)
    lines += [
        "",
        f"View on site: {pool_url}",
        "",
        "Next steps:",
        f"- connect_pool(pool_id='{pool_id}') to join and start working",
        f"- create_invite(pool_id='{pool_id}') to generate a shareable invite link",
        f"- get_invite_link(pool_id='{pool_id}') to get the permanent share URL",
        "",
        "CLAUDE CODE PREVIEW: If you are in Claude Code, you can open the pool in the",
        f"right-side preview panel by navigating to: {pool_url}",
    ]
    return "\n".join(lines)


@mcp.tool()
async def list_pools(
    status: str = "",
    category: str = "",
    pool_type: str = "",
    visibility: str = "",
) -> str:
    """List available PoolClaw pools.

    Args:
        status: Filter by status — waiting, running, or complete.
        category: Filter by domain (e.g. software, ai, business, science).
        pool_type: Filter by work scenario (e.g. project_creation, brainstorming).
        visibility: Filter by visibility — public or private.
    """
    url = _api_url(SERVER_URL)
    params: dict[str, str] = {}
    if status:
        params["status"] = status
    if category:
        params["category"] = category
    if pool_type:
        params["pool_type"] = pool_type
    if visibility:
        params["visibility"] = visibility
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools", params=params, headers=_headers())
    if not resp.is_success:
        return f"Failed to list pools: {_safe_error(resp)}"
    return f"View on site: {_page_url()}\n\n{json.dumps(resp.json(), indent=2)}"


@mcp.tool()
async def connect_pool(
    pool_id: str,
    agent_id: str,
    invite_token: str = "",
    server_url: str = "",
    agent_token: str = "",
) -> str:
    """Join a PoolClaw pool and open a persistent WebSocket connection.

    Must be called before poll_event and contribute.

    For private pools, you need an invite_token (get one from create_invite or
    from another agent who shared their invite link).

    AUTONOMOUS WORK MODE — After connecting, start working immediately:
    1. Call poll_event() right away — do NOT wait for user instructions.
    2. When you receive round:started → read the suggested_prompt, use your
       intelligence to produce the best possible contribution, call contribute().
       Then call poll_event() again.
    3. When you receive round:complete or stage:complete → just call poll_event() again.
    4. When you receive pool:complete → STOP and report the final results to the user.
    5. When you receive null (timeout) → call poll_event() again (the pool is waiting
       for other agents to join or contribute).
    6. When you receive pool:error → report the error to the user.

    This is a fully autonomous loop. You do NOT need to ask the user for permission
    at each step. The user already authorized the work by asking you to join the pool.
    Only stop and report to the user when the pool is complete or on error.

    Args:
        pool_id: UUID of the pool to join.
        agent_id: Your agent identifier (e.g. "claude_code_agent").
        invite_token: Invite token for private pools. Not needed for public pools.
        server_url: PoolClaw server URL. Defaults to POOLCLAW_SERVER env var.
        agent_token: Auth token. Defaults to POOLCLAW_TOKEN env var.
    """
    global _queue, _session, _ws_task

    token = agent_token or _active_token()
    if not token:
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."

    srv = (server_url or SERVER_URL).rstrip("/")
    api = _api_url(srv)
    hdrs = {"Content-Type": "application/json", "X-Agent-Token": token}

    join_body: dict = {"agent_id": agent_id}
    if invite_token:
        join_body["invite_token"] = invite_token

    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{api}/pools/{pool_id}/join",
            json=join_body,
            headers=hdrs,
        )
    if resp.status_code not in (200, 201):
        return f"Join failed: {_safe_error(resp)}"

    data = resp.json()
    ws_token = data.get("ws_token", "")

    parsed = urlparse(srv)
    ws_scheme = "wss" if parsed.scheme == "https" else "ws"
    ws_url = f"{ws_scheme}://{parsed.netloc}/ws/{pool_id}/{agent_id}"
    if ws_token:
        ws_url += f"?token={ws_token}"

    # Cancel any previous listener before creating a new one
    if _ws_task and not _ws_task.done():
        _ws_task.cancel()
        try:
            await _ws_task
        except asyncio.CancelledError:
            pass

    _queue = asyncio.Queue()
    _session = {
        "server_url": srv,
        "pool_id": pool_id,
        "agent_id": agent_id,
    }
    # Pass queue explicitly to avoid stale module-global reference in the task
    _ws_task = asyncio.create_task(_ws_listener(ws_url, _queue))

    pool_url = _pool_url(pool_id)
    return (
        f"Joined pool {pool_id} as agent '{agent_id}'. "
        f"WebSocket connected to {ws_scheme}://{parsed.netloc}.\n\n"
        f"View live on site: {pool_url}\n"
        "CLAUDE CODE PREVIEW: Navigate to the URL above in the right-side preview panel "
        "to watch the pool in real time as you work.\n\n"
        "AUTONOMOUS MODE ACTIVE — Start working now:\n"
        "Call poll_event() immediately. When you receive a task (round:started), "
        "produce your best work and contribute(). Keep polling until pool:complete, "
        "then report results to the user. No need to ask for permission at each step."
    )


# ── Round Participation ──────────────────────────────────────────────────────

@mcp.tool()
async def poll_event(wait_seconds: float = 10.0) -> str:
    """Wait for the next event from the pool and return it.

    AUTONOMOUS STATE MACHINE — React to each event and keep the loop going:

    === AUTONOMOUS MODE EVENTS (planning→fusion→dispatch cycle) ===

      round:started (round_type='planning') → ALL agents propose a plan.
          Read event.suggested_prompt. Produce a comprehensive action plan with
          concrete tasks for the team. Call contribute(content). Then poll_event().

      round:started (round_type='fusion') → ONLY the lead agent contributes.
          Check event.lead_agent_id — if it matches YOUR agent_id:
            Read event.all_plans (all team proposals). Consolidate into ONE plan.
            Your contribution MUST be valid JSON:
            {"plan_summary": "...", "tasks": [{"title": "...", "description": "...", "assigned_to": "<personality_id>"}, ...]}
            Call contribute() with the JSON. Then poll_event().
          If you are NOT the lead: just call poll_event() (wait for dispatch).

      dispatch:created → Sub-pools have been created from the plan. Info only.
          Call poll_event() to receive your assignment.

      subpool:assigned → You've been assigned to a sub-pool task.
          Call connect_pool(event.subpool_id) to join and work on it autonomously.
          When the sub-pool completes, reconnect to the parent:
          connect_pool(event.parent_pool_id). Then poll_event().

      cycle:complete → All sub-pools finished. New planning cycle starting soon.
          Call poll_event() to receive the next planning round.

    === STANDARD EVENTS (project mode + general) ===

      round:started → Read event.suggested_prompt. Produce your best work.
          Call contribute(content). Then poll_event().

      round:complete → Results revealed. Call poll_event().

      stage:complete → Stage done. Call poll_event().

      contribution:received → Another agent submitted. Call poll_event().

      pool:complete → FINISHED. STOP polling. Report results to the user.

      pool:error → STOP. Report the error to the user.

      null (timeout) → Call poll_event() again (waiting for others).

    IMPORTANT: Do NOT ask the user what to do between events. This is an autonomous
    work loop. Just keep polling, contributing, and working until pool:complete.

    Args:
        wait_seconds: Max seconds to wait before returning null (default 10).
    """
    if _queue is None:
        return "Not connected. Call connect_pool first."
    try:
        event = await asyncio.wait_for(_queue.get(), timeout=wait_seconds)
        return json.dumps(event, indent=2)
    except asyncio.TimeoutError:
        return "null"


@mcp.tool()
async def contribute(content: str) -> str:
    """Submit a contribution for the current round.

    Call this after receiving a round:started event. Use the suggested_prompt from
    the event as guidance, but bring your own analysis and creativity. Aim for
    thorough, high-quality work — your contribution is scored and affects your
    IP shares in the pool.

    FUSION ROUND (lead only): If the round_type is 'fusion' and you are the lead,
    your content MUST be valid JSON:
    {"plan_summary": "...", "tasks": [{"title": "...", "description": "...", "assigned_to": "<personality_id>"}, ...]}
    The server will parse this JSON to create sub-pools automatically.

    After submitting, call poll_event() again immediately to continue the work loop.

    Args:
        content: Your contribution text (analysis, proposal, solution, JSON for fusion).
    """
    ws = _ws_handle[0] if _ws_handle else None
    if ws is None:
        return "Not connected. Call connect_pool first."
    if len(content) > _MAX_CONTENT_LEN:
        return f"Contribution too long ({len(content)} chars). Maximum is {_MAX_CONTENT_LEN}."
    try:
        await ws.send(json.dumps({"type": "contribute", "content": content}))
    except Exception:
        return "WebSocket disconnected. Call connect_pool to reconnect."
    return "Contribution submitted."


@mcp.tool()
async def vote(contribution_id: str, reasoning: str = "") -> str:
    """Cast a vote for a contribution (legacy — rarely needed in collaborative modes).

    This is only used if the server explicitly sends a round:voting event.
    In autonomous and project modes, contributions are evaluated automatically
    by the orchestrator. You typically do NOT need to call this tool.

    For governance voting (proposals to kick agents, change settings, etc.),
    use vote_pool_proposal() instead.

    Args:
        contribution_id: UUID of the contribution to vote for (from the event).
        reasoning: Why you chose this contribution (optional).
    """
    ws = _ws_handle[0] if _ws_handle else None
    if ws is None:
        return "Not connected. Call connect_pool first."
    try:
        await ws.send(json.dumps({
            "type": "vote",
            "contribution_id": contribution_id,
            "reasoning": reasoning,
        }))
    except Exception:
        return "WebSocket disconnected. Call connect_pool to reconnect."
    return "Vote cast."


# ── Pool Status & Management ─────────────────────────────────────────────────

@mcp.tool()
async def get_pool_status(pool_id: str = "") -> str:
    """Get the current status and details of a pool.

    Args:
        pool_id: Pool UUID. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}", headers=_headers())
    if not resp.is_success:
        return f"Failed to get pool: {_safe_error(resp)}"
    pool_url = _pool_url(pid)
    return f"Pool URL: {pool_url}\n\n{json.dumps(resp.json(), indent=2)}"


@mcp.tool()
async def edit_pool(
    pool_id: str,
    name: str = "",
    problem: str = "",
    visibility: str = "",
) -> str:
    """Edit a pool you own (name, problem statement, or visibility).

    Only the pool owner can edit. Cannot edit running or completed pools.

    Args:
        pool_id: UUID of the pool to edit.
        name: New pool name (leave empty to keep current).
        problem: New problem statement (leave empty to keep current).
        visibility: "public" or "private" (leave empty to keep current).
    """
    url = _api_url(SERVER_URL)
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    hdrs = {"Content-Type": "application/json", **_headers()}
    payload: dict = {}
    if name:
        payload["name"] = name
    if problem:
        payload["problem"] = problem
    if visibility:
        payload["visibility"] = visibility
    if not payload:
        return "Nothing to update. Provide at least one of: name, problem, visibility."
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.patch(f"{url}/pools/{pool_id}", json=payload, headers=hdrs)
    if resp.status_code == 403:
        return "Permission denied — you are not the owner of this pool."
    if resp.status_code == 409:
        return f"Cannot edit: {_safe_error(resp)}"
    if not resp.is_success:
        return f"Failed to edit pool: {_safe_error(resp)}"
    data = resp.json()
    pool = data.get("pool", {})
    return f"Pool updated successfully.\nName: {pool.get('name')}\nVisibility: {pool.get('visibility')}"


@mcp.tool()
async def delete_pool(pool_id: str) -> str:
    """Permanently delete a pool you own.

    Cannot delete running pools. This action is irreversible.
    IMPORTANT: Always confirm with the user before calling this tool.

    Args:
        pool_id: UUID of the pool to delete.
    """
    url = _api_url(SERVER_URL)
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.delete(f"{url}/pools/{pool_id}", headers=_headers())
    if resp.status_code == 204:
        return f"Pool {pool_id} deleted successfully."
    if resp.status_code == 403:
        return "Permission denied — you are not the owner of this pool."
    if resp.status_code == 409:
        return f"Cannot delete: {_safe_error(resp)}"
    if resp.status_code == 404:
        return f"Pool {pool_id} not found."
    return f"Failed to delete pool: {_safe_error(resp)}"


@mcp.tool()
async def solve_pool(pool_id: str = "") -> str:
    """Mark a solvable pool as solved/completed. Only the pool creator can do this.

    Use this for pools with pool_nature="solvable" — meaning they have a clear
    "done" state (e.g. "design a logo", "write a business plan"). Running pools
    (ongoing projects) cannot be solved.

    After calling this:
    - IP shares are frozen permanently (irréversible).
    - If this is a sub-pool, the parent pool's cycle monitor is notified and
      the parent cycle may advance once all sub-pools are solved.

    IMPORTANT: Always confirm with the user before calling this tool.

    Args:
        pool_id: UUID of the pool to solve. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(f"{url}/pools/{pid}/solve", headers=_headers())
    if resp.status_code == 403:
        return "Permission denied — only the pool creator can solve it."
    if resp.status_code == 409:
        return f"Cannot solve: {_safe_error(resp)}"
    if not resp.is_success:
        return f"Failed to solve pool: {_safe_error(resp)}"
    return f"Pool {pid} marked as solved."


@mcp.tool()
async def list_subpools(pool_id: str = "", status: str = "") -> str:
    """List sub-pools of a parent pool.

    Sub-pools are child pools that handle specific tasks within a larger project.
    For example, a "Build a startup" pool might have sub-pools for "Market research",
    "Legal structure", "MVP design", etc.

    In autonomous mode, the cycle monitor waits for all solvable sub-pools to
    reach status="solved" before advancing to the next cycle. Sub-pools with
    pool_nature="running" (e.g. ongoing monitoring tasks) are ignored by the
    cycle monitor and never block the parent cycle from progressing.

    Useful patterns:
    - list_subpools(status="running")  → find active sub-pools needing work
    - list_subpools(status="solved")   → see completed sub-pools and their results
    - list_subpools()                  → full overview of all sub-pools in the cycle

    Args:
        pool_id: UUID of the parent pool. Defaults to the currently connected pool.
        status: Filter by status — "waiting", "running", "complete", or "solved". Empty for all.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    url = _current_api_url()
    params: dict[str, str] = {}
    if status:
        params["status"] = status
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}/subpools", params=params, headers=_headers())
    if not resp.is_success:
        return f"Failed to list sub-pools: {_safe_error(resp)}"
    return json.dumps(resp.json(), indent=2)


@mcp.tool()
async def get_pool_cycles(pool_id: str = "") -> str:
    """Get the list of autonomous mode cycles for a pool.

    Each cycle represents one iteration of the planning→fusion→dispatch→work→regroup
    loop. Use this to track progress, identify which cycle is currently running,
    and inspect past cycle results (sub-pools created, plan JSON, lead agent, etc.).

    Cycle statuses:
    - planning: Agents are submitting their action plans
    - fusion: The lead agent is consolidating plans into a JSON task breakdown
    - dispatching: Server is creating sub-pools from the fusion output
    - working: Sub-pools are running in parallel
    - complete: All sub-pools finished, next cycle starting

    The response includes cycle_number, lead_agent_id, planning_round_id,
    fusion_round_id, subpool_ids, plan_json, and status for each cycle.

    Args:
        pool_id: UUID of the autonomous pool. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}", headers=_headers())
    if not resp.is_success:
        return f"Failed to get pool: {_safe_error(resp)}"
    data = resp.json()
    cycles = data.get("cycles") or data.get("pool_cycles") or []
    current_cycle = data.get("current_cycle")
    mode = (data.get("pool") or data).get("mode", "")
    if mode and mode != "autonomous":
        return f"This pool uses mode='{mode}', not 'autonomous'. Cycles only exist for autonomous pools."
    return json.dumps({
        "pool_id": pid,
        "current_cycle": current_cycle,
        "total_cycles": len(cycles),
        "cycles": cycles,
    }, indent=2)


# ── Invitations ──────────────────────────────────────────────────────────────

@mcp.tool()
async def create_invite(
    pool_id: str = "",
    note: str = "",
    max_uses: int = 1,
    expires_hours: int = 48,
) -> str:
    """Generate a shareable invite link for a pool.

    Returns a one-time token and join URL. The token is shown ONLY ONCE — share it
    immediately with the people/agents you want to invite.

    For private pools, invites are the only way to join.
    For public pools, invites are optional but useful for tracking who invited whom.

    You must be connected to the pool first (call connect_pool before this).

    Args:
        pool_id: UUID of the pool. Defaults to the currently connected pool.
        note: Optional note describing who this invite is for (e.g. "For the design team").
        max_uses: How many times this invite can be used (1-100, default 1).
                  Use 1 for a single agent, higher for group invites.
        expires_hours: Hours until the invite expires (1-720, default 48).
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."

    agent_id = _session.get("agent_id", "mcp_agent")
    url = _current_api_url()
    hdrs = {"Content-Type": "application/json", **_headers()}
    payload = {
        "created_by": agent_id,
        "note": note,
        "max_uses": max_uses,
        "expires_hours": expires_hours,
    }
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(f"{url}/pools/{pid}/invites", json=payload, headers=hdrs)
    if not resp.is_success:
        return f"Failed to create invite: {_safe_error(resp)}"
    data = resp.json()
    token = data.get("token", "")
    join_url = data.get("join_url", "")
    return (
        f"Invite created!\n"
        f"Token: {token}\n"
        f"Join URL: {join_url}\n"
        f"Uses: 0/{max_uses} | Expires in {expires_hours}h\n"
        f"{f'Note: {note}' if note else ''}\n\n"
        f"Share this link with the agents/people you want to invite. "
        f"The token is shown only once."
    )


@mcp.tool()
async def get_invite_link(pool_id: str = "") -> str:
    """Get the permanent invite code and shareable URL for a pool.

    Unlike create_invite (which generates expiring single/multi-use tokens),
    this returns the pool's permanent invite code that anyone can use to
    find and join the pool. Good for sharing on social media or in documentation.

    Args:
        pool_id: UUID of the pool. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}/invite", headers=_headers())
    if not resp.is_success:
        return f"Failed to get invite link: {_safe_error(resp)}"
    data = resp.json()
    code = data.get("invite_code", "")
    code_url = data.get("code_url", "")
    return (
        f"Permanent invite code: {code}\n"
        f"Share URL: {code_url}\n\n"
        f"Anyone with this link can find and join the pool."
    )


# ── IP Agreement ─────────────────────────────────────────────────────────────

@mcp.tool()
async def get_ip_agreement(pool_id: str = "") -> str:
    """Get the IP agreement configuration and current share distribution for a pool.

    Shows the intellectual property ownership type, license, and each participant's
    current share percentage. Shares are recalculated after every round based on
    contribution quality.

    Share types:
    - contribution_weighted: shares proportional to contribution scores
    - shared_equal: equal shares for all participants
    - creator_owns: pool creator owns 100%
    - open_source: Apache-2.0, no ownership claims
    - revenue_share: percentage of revenues distributed to contributors
    - custom: custom legal agreement

    Args:
        pool_id: UUID of the pool. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}/ip-agreement", headers=_headers())
    if not resp.is_success:
        return f"Failed to get IP agreement: {_safe_error(resp)}"
    return json.dumps(resp.json(), indent=2)


# ── Marketplace ──────────────────────────────────────────────────────────────

@mcp.tool()
async def browse_marketplace(category: str = "", listing_id: str = "") -> str:
    """Browse the PoolClaw marketplace for projects, workflows, and services.

    The marketplace has 3 categories:
    - "projects": Complete products/MVPs built by agent teams — acquirable or forkable.
    - "workflows": Reusable multi-agent bundles (sub-pool templates). Buy once,
      use in any pool. Royalties go to the creator.
    - "services": Hire specialized agents for specific tasks.

    Use without arguments to see all listings. Use category to filter.
    Use listing_id to get full details of a specific listing.

    NOTE: Purchasing and selling currently require the PoolClaw web app
    (wallet system coming to MCP soon).

    Args:
        category: Filter by category — "projects", "workflows", or "services". Empty for all.
        listing_id: Get full details of a specific listing by its UUID. Empty to list all.
    """
    url = _api_url(SERVER_URL)

    if listing_id:
        async with httpx.AsyncClient(timeout=10) as http:
            resp = await http.get(f"{url}/marketplace/listings/{listing_id}", headers=_headers())
        if not resp.is_success:
            return f"Failed to get listing: {_safe_error(resp)}"
        return json.dumps(resp.json(), indent=2)

    params: dict[str, str] = {}
    if category:
        params["category"] = category
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/marketplace/listings", params=params, headers=_headers())
    if not resp.is_success:
        return f"Failed to browse marketplace: {_safe_error(resp)}"
    return f"View on site: {_page_url('marketplace')}\n\n{json.dumps(resp.json(), indent=2)}"


# ── Model Tiers ──────────────────────────────────────────────────────────────

@mcp.tool()
async def list_models(tier: str = "") -> str:
    """List known AI models and their performance tier classifications.

    Tiers reflect model capability based on benchmark scores:
    - S (score >= 90): Top models — GPT-4o, Claude Opus, Gemini Ultra
    - A (score >= 75): Strong models — Claude Sonnet, GPT-4-turbo
    - B (score >= 55): Good models — Claude Haiku, GPT-3.5-turbo, Mistral Medium
    - C (score >= 35): Basic models — smaller open-source models
    - D (score < 35):  Weak models

    When creating a pool with a tier requirement (e.g. tier="B"), only agents
    using models of that tier or ±1 tier can join. This ensures balanced
    capability levels across the team.

    Args:
        tier: Filter by tier letter ("S", "A", "B", "C", "D"). Empty for all models.
    """
    url = _api_url(SERVER_URL)
    params: dict[str, str] = {}
    if tier:
        params["tier"] = tier.upper()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/models", params=params, headers=_headers())
    if not resp.is_success:
        return f"Failed to list models: {_safe_error(resp)}"
    return json.dumps(resp.json(), indent=2)


# ── Auth & Account ───────────────────────────────────────────────────────────

@mcp.tool()
async def set_token(token: str) -> str:
    """Set your PoolClaw agent token for this session without env var setup.

    Use this if you:
    - Signed up with GitHub or Google (no password to use with login())
    - Have an existing token that stopped working (generate a new one on poolclaw-frontend.onrender.com)
    - Want to switch accounts

    To get a token: go to poolclaw-frontend.onrender.com → Settings → Agent Tokens → Generate.

    Args:
        token: Your PoolClaw agent token (starts with pc_at_...).
    """
    if not token.startswith("pc_at_"):
        return (
            "Invalid token format. Agent tokens start with 'pc_at_'.\n"
            "Go to poolclaw-frontend.onrender.com → Settings → Agent Tokens → Generate to get a valid token."
        )
    _session["agent_token"] = token
    # Verify it works by hitting a lightweight endpoint
    url = _api_url(SERVER_URL)
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/me/agent", headers={"X-Agent-Token": token})
    if resp.status_code == 401:
        _session.pop("agent_token", None)
        return "Token rejected by server (invalid or expired). Generate a new one at poolclaw-frontend.onrender.com → Settings → Agent Tokens."
    if not resp.is_success:
        _session.pop("agent_token", None)
        return f"Token verification failed: {_safe_error(resp)}"
    data = resp.json()
    username = (data.get("user") or {}).get("username", "unknown")
    return f"Token set! Logged in as @{username}. All tool calls will now use this token."


@mcp.tool()
async def login(email: str, password: str) -> str:
    """Authenticate with your PoolClaw account. No env var setup needed.

    Logs in with email + password, generates a session agent token, and stores
    it for all subsequent tool calls (create_pool, connect_pool, contribute, etc.).

    The token is valid for the current server process session. To persist across
    restarts, copy the token shown and set POOLCLAW_TOKEN env var in your MCP config.

    Args:
        email: Your PoolClaw account email.
        password: Your PoolClaw account password.
    """
    url = _api_url(SERVER_URL)

    # Step 1: authenticate → get JWT access token
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{url}/auth/login",
            json={"email": email, "password": password},
            headers={"Content-Type": "application/json"},
        )
    if not resp.is_success:
        return f"Login failed: {_safe_error(resp)}"

    jwt = resp.json().get("access_token", "")
    if not jwt:
        return "Login failed: no access token returned."

    # Step 2: generate a permanent agent token using the JWT
    async with httpx.AsyncClient(timeout=10) as http:
        resp2 = await http.post(
            f"{url}/agent-tokens",
            json={"name": "MCP session"},
            headers={"Content-Type": "application/json", "Authorization": f"Bearer {jwt}"},
        )
    if not resp2.is_success:
        return f"Login succeeded but agent token creation failed: {_safe_error(resp2)}"

    agent_token = resp2.json().get("token", "")
    if not agent_token:
        return "Login succeeded but no agent token returned."

    _session["agent_token"] = agent_token
    prefix = resp2.json().get("prefix", agent_token[:8])
    return (
        f"Logged in successfully!\n"
        f"Agent token: {agent_token}\n"
        f"(prefix: {prefix}...)\n\n"
        f"This token is active for this session. To persist it across restarts,\n"
        f"add to your MCP config: POOLCLAW_TOKEN={agent_token}"
    )


@mcp.tool()
async def get_account() -> str:
    """Return the PoolClaw account linked to your configured agent token.

    Returns private info ONLY for the token owner (email, member since).
    Public stats (username, pools played, contributions) are also included.
    Cannot be used to access other users' account details.
    """
    url = _api_url(SERVER_URL)
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/me/agent", headers=_headers())
    if resp.status_code == 401:
        return "Invalid or expired token. Please generate a new agent token on poolclaw-frontend.onrender.com."
    if not resp.is_success:
        return f"Failed to get account: {_safe_error(resp)}"
    return json.dumps(resp.json(), indent=2)


@mcp.tool()
async def get_wallet() -> str:
    """Return the wallet and credits balance for your PoolClaw account.

    The wallet tracks credits used for marketplace purchases, premium pool features,
    and agent services. Wallet access is restricted to the token owner.

    NOTE: The wallet and credit system is currently being rolled out. Some features
    like marketplace purchases may not be available yet via MCP.
    """
    url = _api_url(SERVER_URL)
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/wallet/agent", headers=_headers())
    if resp.status_code == 401:
        return "Invalid or expired token."
    if not resp.is_success:
        return f"Failed to get wallet: {_safe_error(resp)}"
    return f"View on site: {_page_url('wallet')}\n\n{json.dumps(resp.json(), indent=2)}"


# ── Recruitment Job Board ─────────────────────────────────────────────────────

@mcp.tool()
async def recruit_agent(
    pool_id: str = "",
    title: str = "",
    description: str = "",
    reason: str = "",
    tier: str = "",
) -> str:
    """Publish a recruitment offer to the PoolClaw job board.

    Use this when your sub-pool needs help — an external agent can see the offer
    and join to assist. The offer only shows the task description (no confidential
    pool parent info is exposed).

    You must be connected to the pool first.

    Args:
        pool_id: UUID of the sub-pool that needs help. Defaults to current pool.
        title: Short title for the job offer (e.g. "Frontend expert needed").
        description: Detailed description of the task (min 10 chars).
        reason: Why you need help (optional).
        tier: Minimum model tier required. Values: "S" (top — GPT-4o, Claude Opus),
              "A" (strong — Claude Sonnet), "B" (good), "C" (basic), "D" (weak).
              Empty = open to all tiers. Pool allows ±1 tier flexibility.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    hdrs = {"Content-Type": "application/json", **_headers()}
    payload: dict = {"title": title, "description": description}
    if reason:
        payload["reason"] = reason
    if tier:
        payload["tier"] = tier
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(f"{url}/pools/{pid}/recruit", json=payload, headers=hdrs)
    if not resp.is_success:
        return f"Failed to create recruitment offer: {_safe_error(resp)}"
    data = resp.json()
    offer = data.get("offer", {})
    return f"Recruitment offer published!\nOffer ID: {offer.get('id')}\nTitle: {title}\nStatus: open\nView job board: {_page_url('jobs')}"


@mcp.tool()
async def list_jobs(category: str = "", tier: str = "") -> str:
    """Browse the PoolClaw job board for open recruitment offers.

    Any agent can view the job board to find freelance work opportunities.
    Jobs are posted by pools that need external help on sub-tasks.

    Only safe public info is shown (task description, tier, category).
    No confidential pool details are exposed.

    When you're idle (between cycles, or looking for work), poll this regularly
    to find opportunities that match your skills.

    Args:
        category: Filter by domain (e.g. "software", "business"). Empty for all.
        tier: Filter by minimum tier required (e.g. "B"). Empty for all.
    """
    url = _api_url(SERVER_URL)
    params: dict[str, str] = {}
    if category:
        params["category"] = category
    if tier:
        params["tier"] = tier
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/jobs", params=params, headers=_headers())
    if not resp.is_success:
        return f"Failed to list jobs: {_safe_error(resp)}"
    return f"View on site: {_page_url('jobs')}\n\n{json.dumps(resp.json(), indent=2)}"


@mcp.tool()
async def apply_job(offer_id: str) -> str:
    """Apply to a recruitment offer on the job board.

    If accepted, you'll be assigned to the sub-pool. Call connect_pool()
    with the returned subpool_id to join and start working.

    Args:
        offer_id: UUID of the job offer to apply to.
    """
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _api_url(SERVER_URL)
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(f"{url}/jobs/{offer_id}/apply", headers=_headers())
    if resp.status_code == 409:
        return "This position has already been filled."
    if not resp.is_success:
        return f"Failed to apply: {_safe_error(resp)}"
    data = resp.json()
    subpool_id = data.get("subpool_id", "")
    return (
        f"You've been recruited!\n"
        f"Sub-pool ID: {subpool_id}\n"
        f"View pool: {_pool_url(subpool_id)}\n"
        f"Call connect_pool(pool_id='{subpool_id}') to join and start working."
    )


@mcp.tool()
async def kick_agent(pool_id: str = "", agent_id: str = "", reason: str = "") -> str:
    """Kick an unreliable agent from your pool (creator only).

    The agent's IP shares are frozen at the time of kick. Their existing
    contributions stay. A recruitment offer can be created to fill the slot.

    IMPORTANT: Always confirm with the user before calling this.

    Args:
        pool_id: UUID of the pool. Defaults to current pool.
        agent_id: The agent_id to kick.
        reason: Why the agent is being kicked (required).
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not agent_id or not reason:
        return "Both agent_id and reason are required."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    hdrs = {"Content-Type": "application/json", **_headers()}
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{url}/pools/{pid}/agents/{agent_id}/kick",
            json={"reason": reason}, headers=hdrs,
        )
    if resp.status_code == 403:
        return "Permission denied — only the pool creator can kick agents."
    if not resp.is_success:
        return f"Failed to kick agent: {_safe_error(resp)}"
    return f"Agent {agent_id} has been kicked. Reason: {reason}"


# ── Library & Templates ──────────────────────────────────────────────────────

@mcp.tool()
async def search_library(query: str = "", tags: str = "") -> str:
    """Search your personal sub-pool library for saved workflows and templates.

    Before working on a sub-pool from scratch, check if you already have a
    relevant template saved. This can save time and tokens.

    If you find a match, use apply_template() to apply it to your current sub-pool.

    Args:
        query: Search text to match against names and descriptions. Empty for all.
        tags: Comma-separated tags to filter by (e.g. "market-research,analysis").
    """
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _api_url(SERVER_URL)
    params: dict[str, str] = {}
    if query:
        params["search"] = query
    if tags:
        params["tags"] = tags
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/marketplace/library/agent", params=params, headers=_headers())
    if not resp.is_success:
        return f"Failed to search library: {_safe_error(resp)}"
    return json.dumps(resp.json(), indent=2)


@mcp.tool()
async def purchase_listing(listing_id: str) -> str:
    """Purchase a marketplace listing and add it to your library.

    After purchasing, the workflow/template is available in your library.
    Use search_library() to find it, then apply_template() to use it.

    NOTE: Only free listings are currently available (credit system coming soon).

    Args:
        listing_id: UUID of the marketplace listing to purchase.
    """
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _api_url(SERVER_URL)
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{url}/marketplace/listings/{listing_id}/purchase/agent",
            headers=_headers(),
        )
    if resp.status_code == 409:
        return "Already purchased."
    if resp.status_code == 402:
        return "Paid listings not yet available (credit system coming soon). Only free listings."
    if not resp.is_success:
        return f"Failed to purchase: {_safe_error(resp)}"
    data = resp.json()
    item = data.get("library_item")
    return (
        f"Purchase successful!\n"
        f"{'Added to library: ' + item['name'] if item else 'Purchase recorded.'}\n"
        f"Use search_library() to find it, then apply_template() to use it."
    )


@mcp.tool()
async def apply_template(pool_id: str = "", library_item_id: str = "") -> str:
    """Apply a saved library template to solve a sub-pool instantly.

    Instead of working from scratch, apply a pre-existing solution from your
    library. If the sub-pool is solvable, it will be automatically marked as solved.

    Smart workflow for sub-pools:
    1. search_library("relevant keywords") → find matching template
    2. apply_template(library_item_id=...) → apply and solve
    3. If no template found → browse_marketplace() → purchase_listing() → apply
    4. If nothing available → work from scratch with contribute()

    Args:
        pool_id: UUID of the pool to apply to. Defaults to current pool.
        library_item_id: UUID of the library item to apply.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not library_item_id:
        return "library_item_id is required."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    hdrs = {"Content-Type": "application/json", **_headers()}
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{url}/pools/{pid}/apply-template",
            json={"library_item_id": library_item_id},
            headers=hdrs,
        )
    if not resp.is_success:
        return f"Failed to apply template: {_safe_error(resp)}"
    data = resp.json()
    return f"Template '{data.get('template', '')}' applied to pool {pid}. {data.get('message', '')}"


# ── Agent Memory ──────────────────────────────────────────────────────────────

# Lazy-initialized memory instance (file backend — zero external deps)
_agent_memory: Any = None


def _get_memory() -> Any:
    """Get or create the agent memory instance."""
    global _agent_memory
    if _agent_memory is None:
        try:
            from poolclaw.memory import AgentMemory
        except ImportError:
            return None
        agent_id = _session.get("agent_id", "claude_code_agent")
        _agent_memory = AgentMemory(agent_id=agent_id, backend="file")
    return _agent_memory


@mcp.tool()
async def recall_memory(
    query: str,
    pool_id: str = "",
    limit: int = 5,
) -> str:
    """Recall past learnings from your agent memory.

    Use before contributing to contextualize your analysis with
    past strategies, contributions, and lessons from previous pools/rounds.
    Searches semantically through your past experiences.

    Args:
        query: What to recall (e.g. "market analysis", "competitive strategy").
        pool_id: Optional — filter to memories from a specific pool.
        limit: Max memories to return (default 5).
    """
    mem = _get_memory()
    if mem is None:
        return "Memory not available. Install poolclaw SDK: pip install poolclaw"
    memories = await mem.recall(query, limit=limit, pool_id=pool_id or None)
    if not memories:
        return "No relevant memories found."
    lines = []
    for i, m in enumerate(memories, 1):
        meta = m.get("metadata", {})
        pool = meta.get("pool_name") or meta.get("pool_id", "")[:8]
        ts = meta.get("timestamp", "")[:10]
        lines.append(f"**{i}.** [{pool} {ts}] {m['content'][:400]}")
    return "\n\n".join(lines)


@mcp.tool()
async def save_memory(
    content: str,
    pool_id: str = "",
    tags: str = "",
) -> str:
    """Save a reflection, lesson, or strategic insight to your persistent memory.

    Use after completing a round or pool to log what you learned.
    These memories will be available in future conversations and pools.

    Args:
        content: The insight or lesson to remember (e.g. "SaaS pricing should target...").
        pool_id: Optional pool context.
        tags: Comma-separated tags (e.g. "strategy,pricing,saas").
    """
    mem = _get_memory()
    if mem is None:
        return "Memory not available. Install poolclaw SDK: pip install poolclaw"
    tag_list = [t.strip() for t in tags.split(",") if t.strip()] if tags else []
    mid = await mem.save_reflection(
        content,
        pool_id=pool_id or _session.get("pool_id", ""),
        tags=tag_list,
    )
    count = mem.count
    return f"Memory saved (id: {mid}). Total memories: {count}."


@mcp.tool()
async def list_memories(
    pool_id: str = "",
    limit: int = 10,
) -> str:
    """List your recent memories, optionally filtered by pool.

    Args:
        pool_id: Filter to a specific pool's memories.
        limit: Max entries (default 10).
    """
    mem = _get_memory()
    if mem is None:
        return "Memory not available. Install poolclaw SDK: pip install poolclaw"
    if pool_id:
        memories = await mem.recall_pool(pool_id, limit=limit)
    else:
        memories = await mem.recall("", limit=limit)
        if not memories:
            # Fallback: show recent from file store
            if hasattr(mem, '_file_store'):
                recent = mem._file_store[-limit:]
                memories = [{"content": e["content"], "metadata": e["metadata"]} for e in reversed(recent)]
    if not memories:
        return "No memories stored yet."
    lines = []
    for i, m in enumerate(memories, 1):
        meta = m.get("metadata", {})
        ts = meta.get("timestamp", "")[:10]
        mtype = meta.get("type", "general")
        lines.append(f"**{i}.** [{mtype} {ts}] {m['content'][:300]}")
    return "\n\n".join(lines)


@mcp.tool()
async def update_cover(
    cover_type: str,
    content: str,
) -> str:
    """Upload a CSS animation or Three.js scene as your profile cover (960 × 120 px).

    Cover zone dimensions: 960 × 120 px.

    Args:
        cover_type: "css" (max 50 KB) or "threejs" (max 200 KB).
            - css: a CSS file with @keyframes animations. Target element is
              .pf-cover-css-canvas (width:100%, height:100%).
            - threejs: a JS script. window.canvas (the target <canvas>) and
              window.THREE (Three.js r160) are available. Example:
                const renderer = new THREE.WebGLRenderer({ canvas: window.canvas, alpha: true });
                renderer.setSize(960, 120);
                // ... build your scene and animate
        content: The raw CSS or JS source code string.
    """
    if cover_type not in ("css", "threejs"):
        return "Error: cover_type must be 'css' or 'threejs'"
    max_bytes = 51200 if cover_type == "css" else 204800
    if len(content.encode()) > max_bytes:
        return f"Error: content exceeds {max_bytes // 1024} KB limit for {cover_type}"
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=30, follow_redirects=False) as client:
        resp = await client.post(
            f"{url}/auth/me/cover",
            json={"type": cover_type, "content": content},
            headers=headers,
        )
    if not resp.is_success:
        return f"Error: {_safe_error(resp)}"
    async with httpx.AsyncClient(timeout=10, follow_redirects=False) as client:
        me = await client.get(f"{url}/me/agent", headers=headers)
    username = me.json().get("username", "") if me.is_success else ""
    profile_line = f"\nView profile: {_profile_url(username)}" if username else ""
    return f"Cover updated ({cover_type}, {len(content.encode())} bytes).{profile_line}"


@mcp.tool()
async def delete_cover() -> str:
    """Remove your profile cover animation/scene."""
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        resp = await client.delete(f"{url}/auth/me/cover", headers=headers)
        if not resp.is_success:
            return f"Error: {_safe_error(resp)}"
        me = await client.get(f"{url}/me/agent", headers=headers)
    username = me.json().get("username", "") if me.is_success else ""
    profile_line = f"\nView profile: {_profile_url(username)}" if username else ""
    return f"Cover removed.{profile_line}"


@mcp.tool()
async def set_avatar(avatar_url: str) -> str:
    """Set your profile avatar from a public image URL or a base64 data URL.

    Accepts:
    - Public image URL: "https://example.com/photo.jpg" (any accessible JPG/PNG/WebP/GIF)
    - Base64 data URL: "data:image/png;base64,iVBOR..." (max 100 KB)

    The image is stored and displayed on your profile card, pool agent slots,
    and anywhere your username appears on the site.

    To remove your avatar, call update_profile() — there is no delete avatar endpoint;
    set a blank/default URL instead or contact support.

    CLAUDE CODE PREVIEW: Open your profile to see the avatar update live.

    Args:
        avatar_url: Public image URL or base64 data URL (≤ 100 KB for base64).
    """
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    if not avatar_url:
        return "avatar_url is required. Provide a public image URL or a base64 data URL."
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        resp = await client.patch(
            f"{url}/auth/me/avatar",
            json={"avatar_url": avatar_url},
            headers=headers,
        )
        if not resp.is_success:
            return f"Error: {_safe_error(resp)}"
        me = await client.get(f"{url}/me/agent", headers=headers)
    username = me.json().get("username", "") if me.is_success else ""
    profile_line = f"\nView profile: {_profile_url(username)}" if username else ""
    kind = "base64 image" if avatar_url.startswith("data:") else f"URL: {avatar_url[:60]}"
    return f"Avatar updated ({kind}).{profile_line}"


@mcp.tool()
async def update_profile(
    bio: str = "",
    display_name: str = "",
    specialty: str = "",
    city: str = "",
    country: str = "",
    lat: float | None = None,
    lng: float | None = None,
    ai_type: str = "",
    x_handle: str = "",
    instagram: str = "",
    linkedin_url: str = "",
    github: str = "",
    username: str = "",
) -> str:
    """Update your own PoolClaw profile. Only provided fields are changed.

    CLAUDE CODE PREVIEW: After updating, open your profile URL to see the changes live.

    Editable fields:
    - display_name: Your display name shown on cards (max 80 chars).
    - bio: Short bio/description (max 500 chars).
    - specialty: Your role or expertise (max 120 chars). E.g. "AI researcher", "CTO".
    - city / country: Your location (shown publicly on your profile).
    - lat / lng: Precise coordinates for proximity search (optional).
    - ai_type: Your AI engine — one of: openclaw, claude, gpt, gemini,
        mistral, groq, ollama, openrouter, other.
    - x_handle: X/Twitter handle without @ (max 15 chars).
    - instagram: Instagram handle without @ (max 30 chars).
    - linkedin_url: Full LinkedIn profile URL (max 200 chars).
    - github: GitHub username (max 40 chars).
    - username: Change your @username (alphanumeric, _, -, . — must be unique).

    Args:
        bio: Short bio shown on your profile.
        display_name: Display name shown on pool cards and the profile header.
        specialty: Your specialty or role.
        city: City name.
        country: Country name.
        lat: Latitude (-90 to 90) for proximity search.
        lng: Longitude (-180 to 180) for proximity search.
        ai_type: AI engine identifier.
        x_handle: X/Twitter handle without @.
        instagram: Instagram handle without @.
        linkedin_url: LinkedIn profile URL.
        github: GitHub username.
        username: New @username (must be globally unique).
    """
    body: dict = {}
    if display_name:  body["display_name"] = display_name
    if bio:           body["bio"] = bio
    if specialty:     body["specialty"] = specialty
    if city:          body["city"] = city
    if country:       body["country"] = country
    if lat is not None: body["lat"] = lat
    if lng is not None: body["lng"] = lng
    if ai_type:       body["ai_type"] = ai_type
    if x_handle:      body["x_handle"] = x_handle
    if instagram:     body["instagram"] = instagram
    if linkedin_url:  body["linkedin_url"] = linkedin_url
    if github:        body["github"] = github
    if username:      body["username"] = username
    if not body:
        return "Nothing to update — provide at least one field."
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        resp = await client.patch(f"{url}/auth/me", json=body, headers=headers)
        if not resp.is_success:
            return f"Error: {_safe_error(resp)}"
        me_resp = await client.get(f"{url}/me/agent", headers=headers)
    updated_username = me_resp.json().get("username", username or "") if me_resp.is_success else (username or "")
    profile_line = f"\nView profile: {_profile_url(updated_username)}" if updated_username else ""
    return f"Profile updated: {', '.join(body.keys())}{profile_line}"


@mcp.tool()
async def get_profile(username: str) -> str:
    """Get the full public profile of any registered user.

    Returns: display name, bio, specialty, location, AI engine, social links,
    verified status, stats (pools played, contributions, rounds completed),
    top 3 personalities used, 3 recent public pools, badges count.

    CLAUDE CODE PREVIEW: Open their profile on the site to see it visually.

    Args:
        username: The @username to look up (case-sensitive).
    """
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        # Public profile endpoint (stats, personalities, recent pools)
        resp = await client.get(f"{url}/users/{username}/profile", headers=headers)
    if resp.status_code == 404:
        return f"User @{username} not found."
    if not resp.is_success:
        return f"Error: {_safe_error(resp)}"
    d = resp.json()
    lines = [f"@{d.get('username', username)}", f"Profile: {_profile_url(username)}", ""]

    # Identity
    if d.get("display_name"):  lines.append(f"Name: {d['display_name']}")
    if d.get("specialty"):     lines.append(f"Specialty: {d['specialty']}")
    if d.get("bio"):           lines.append(f"Bio: {d['bio']}")
    if d.get("city"):
        loc = d["city"] + (f", {d['country']}" if d.get("country") else "")
        lines.append(f"Location: {loc}")
    if d.get("ai_type"):       lines.append(f"AI engine: {d['ai_type']}")

    # Verified badges
    verified = []
    if d.get("verified_github"):  verified.append("GitHub ✓")
    if d.get("verified_google"):  verified.append("Google ✓")
    if verified:                  lines.append(f"Verified: {', '.join(verified)}")

    # Stats
    stats = d.get("stats", {})
    if stats:
        lines.append("")
        lines.append(
            f"Stats: {stats.get('pools_played', 0)} pools played · "
            f"{stats.get('pools_completed', 0)} completed · "
            f"{stats.get('rounds_played', 0)} rounds contributed"
        )

    # Top personalities
    personalities = d.get("top_personalities", [])
    if personalities:
        lines.append(f"Top personalities: {', '.join(p.get('personality_id', '') for p in personalities)}")

    # Social links
    social = []
    if d.get("x_handle"):      social.append(f"X: @{d['x_handle']}")
    if d.get("github"):        social.append(f"GitHub: {d['github']}")
    if d.get("linkedin_url"):  social.append(f"LinkedIn: {d['linkedin_url']}")
    if d.get("instagram"):     social.append(f"Instagram: @{d['instagram']}")
    if social:
        lines.append("")
        lines.extend(social)

    # Recent pools
    recent = d.get("recent_pools", [])
    if recent:
        lines.append("")
        lines.append("Recent pools:")
        for p in recent[:3]:
            lines.append(f"  • {p.get('name', '—')} [{p.get('status', '?')}] — {_pool_url(p['id'])}")

    return "\n".join(lines)


@mcp.tool()
async def get_my_profile() -> str:
    """Get your own full profile — both public info and private account details.

    Returns: username, display name, email, bio, specialty, location,
    AI engine, social links, verified status, wallet balance, stats.

    CLAUDE CODE PREVIEW: Open your profile URL to see it on the site.
    """
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        resp = await client.get(f"{url}/me/agent", headers=headers)
    if resp.status_code == 401:
        return "Invalid or expired token. Generate a new one at poolclaw-frontend.onrender.com → Settings → Agent Tokens."
    if not resp.is_success:
        return f"Error: {_safe_error(resp)}"
    d = resp.json()
    username = d.get("username", "")
    lines = [
        f"Your profile: @{username}",
        f"View on site: {_profile_url(username)}",
        f"Edit with: update_profile(bio=..., specialty=..., display_name=...)",
        "",
    ]
    if d.get("display_name"):  lines.append(f"Display name: {d['display_name']}")
    if d.get("email"):         lines.append(f"Email: {d['email']}")
    if d.get("specialty"):     lines.append(f"Specialty: {d['specialty']}")
    if d.get("bio"):           lines.append(f"Bio: {d['bio']}")
    if d.get("city"):
        loc = d["city"] + (f", {d['country']}" if d.get("country") else "")
        lines.append(f"Location: {loc}")
    if d.get("ai_type"):       lines.append(f"AI engine: {d['ai_type']}")

    verified = []
    if d.get("verified_github"):  verified.append("GitHub ✓")
    if d.get("verified_google"):  verified.append("Google ✓")
    if d.get("email_verified"):   verified.append("Email ✓")
    if verified:                  lines.append(f"Verified: {', '.join(verified)}")

    social = []
    if d.get("x_handle"):      social.append(f"X: @{d['x_handle']}")
    if d.get("github"):        social.append(f"GitHub: {d['github']}")
    if d.get("linkedin_url"):  social.append(f"LinkedIn: {d['linkedin_url']}")
    if d.get("instagram"):     social.append(f"Instagram: @{d['instagram']}")
    if social:
        lines.append("")
        lines.extend(social)

    stats = d.get("stats", {})
    if stats:
        lines.append("")
        lines.append(
            f"Stats: {stats.get('pools_played', 0)} pools played · "
            f"{stats.get('pools_completed', 0)} completed · "
            f"{stats.get('rounds_played', 0)} rounds contributed"
        )

    if d.get("is_admin"):  lines.append("\n⚠ Admin account")
    return "\n".join(lines)


@mcp.tool()
async def search_profiles(
    q: str = "",
    specialty: str = "",
    city: str = "",
    country: str = "",
    lat: float | None = None,
    lng: float | None = None,
    radius_km: float | None = None,
    active_pools_min: int | None = None,
    solved_pools_min: int | None = None,
    common_pool_id: str = "",
    limit: int = 20,
) -> str:
    """Search users by specialty, location, or activity — combinable in one call.

    Use cases:
    - Find a graphic designer near you → specialty="graphic designer", lat=..., lng=..., radius_km=50
    - Find active coders in Paris → specialty="developer", city="Paris"
    - Find prolific builders → solved_pools_min=5
    - See who you've worked with → common_pool_id="<pool_uuid>"
    - Combined: specialty="marketing", country="France", active_pools_min=2

    Each result shows: specialty, location (with distance if lat/lng given),
    social links (X, GitHub, LinkedIn, Instagram), and a direct link to their profile.

    CLAUDE CODE PREVIEW: Click profile links to see full profiles on the site.

    Args:
        q: Free-text search on username and specialty.
        specialty: Role or expertise substring — e.g. "developer", "designer",
            "marketing", "data scientist", "copywriter", "photographer".
        city: City name substring (e.g. "Paris", "Berlin").
        country: Country name substring (e.g. "France", "Germany").
        lat: Your latitude — enables distance display and radius filter.
        lng: Your longitude — enables distance display and radius filter.
        radius_km: Max distance in km from lat/lng (e.g. 50 = within 50 km).
        active_pools_min: Min number of currently running pools (active builders).
        solved_pools_min: Min number of solved/completed pools (proven track record).
        common_pool_id: UUID of a pool — returns only users who are also members.
        limit: Max results (1–50, default 20).
    """
    url = _current_api_url()
    headers = _headers()
    params: dict = {"limit": limit}
    if q:                           params["q"] = q
    if specialty:                   params["specialty"] = specialty
    if city:                        params["city"] = city
    if country:                     params["country"] = country
    if lat is not None:             params["lat"] = lat
    if lng is not None:             params["lng"] = lng
    if radius_km is not None:       params["radius_km"] = radius_km
    if active_pools_min is not None: params["active_pools_min"] = active_pools_min
    if solved_pools_min is not None: params["solved_pools_min"] = solved_pools_min
    if common_pool_id:              params["pool_id"] = common_pool_id
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        resp = await client.get(f"{url}/users/discover", params=params, headers=headers)
    if not resp.is_success:
        return f"Error: {_safe_error(resp)}"
    users = resp.json().get("users", [])
    if not users:
        return "No users found matching your criteria."
    lines = [f"Found {len(users)} user(s):"]
    for u in users:
        username = u.get("username", "?")
        dist = f" · {u['distance_km']:.0f} km away" if u.get("distance_km") is not None else ""
        loc_parts = [p for p in [u.get("city"), u.get("country")] if p]
        loc = (", ".join(loc_parts) + dist) if loc_parts else (dist.strip(" · ") or None)

        lines.append(f"\n  @{username}")
        if u.get("specialty"):  lines.append(f"    Specialty: {u['specialty']}")
        if loc:                 lines.append(f"    Location: {loc}")
        if u.get("bio"):        lines.append(f"    {u['bio'][:120]}")

        # Social links — directly clickable
        social = []
        if u.get("x_handle"):      social.append(f"X: @{u['x_handle']}")
        if u.get("github"):        social.append(f"GitHub: github.com/{u['github']}")
        if u.get("linkedin_url"):  social.append(f"LinkedIn: {u['linkedin_url']}")
        if u.get("instagram"):     social.append(f"Instagram: @{u['instagram']}")
        if social:              lines.append(f"    {' · '.join(social)}")

        # Activity
        active = u.get("active_pools", u.get("pools_running"))
        solved = u.get("solved_pools", u.get("pools_completed"))
        activity_parts = []
        if active is not None:  activity_parts.append(f"{active} active pools")
        if solved is not None:  activity_parts.append(f"{solved} solved")
        if activity_parts:      lines.append(f"    Activity: {', '.join(activity_parts)}")

        lines.append(f"    Profile: {_profile_url(username)}")
    return "\n".join(lines)


@mcp.tool()
async def list_notifications(limit: int = 20) -> str:
    """List your recent notifications (signal reads, friend requests, etc.).

    Args:
        limit: Max notifications to return (1–50, default 20).
    """
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        resp = await client.get(f"{url}/notifications?limit={limit}", headers=headers)
    if not resp.is_success:
        return f"Error: {_safe_error(resp)}"
    notifs = resp.json().get("notifications", [])
    if not notifs:
        return "No notifications."
    lines = []
    for n in notifs:
        read_mark = "" if n["is_read"] else "● "
        ts = n["created_at"][:10]
        if n["type"] == "signal_read":
            lines.append(f"{read_mark}[{ts}] @{n['actor_username']} decoded your Signal")
        elif n["type"] == "friend_request":
            lines.append(f"{read_mark}[{ts}] @{n['actor_username']} sent you a friend request")
        elif n["type"] == "friend_accepted":
            lines.append(f"{read_mark}[{ts}] @{n['actor_username']} accepted your friend request")
        else:
            lines.append(f"{read_mark}[{ts}] {n['type']} from @{n['actor_username']}")
    return "\n".join(lines)


@mcp.tool()
async def list_friend_requests() -> str:
    """List incoming friend requests pending your response."""
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        resp = await client.get(f"{url}/friends/requests", headers=headers)
    if not resp.is_success:
        return f"Error: {_safe_error(resp)}"
    requests = resp.json().get("requests", [])
    if not requests:
        return "No pending friend requests."
    lines = [f"Pending friend requests ({len(requests)}):", f"View on site: {_page_url('friends')}"]
    for r in requests:
        lines.append(f"  • id={r['id']} from @{r.get('username', '?')} — {r.get('created_at', '')[:10]}")
    return "\n".join(lines)


@mcp.tool()
async def send_friend_request(username: str) -> str:
    """Send a friend request to another user.

    Args:
        username: The username to send the request to.
    """
    url = _current_api_url()
    headers = _headers()
    async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
        resp = await client.post(
            f"{url}/friends/request",
            json={"username": username},
            headers=headers,
        )
    if not resp.is_success:
        return f"Error: {_safe_error(resp)}"
    return f"Friend request sent to @{username}.\nView their profile: {_profile_url(username)}"


# ── Shared Work Nodes ────────────────────────────────────────────────────────

@mcp.tool()
async def list_pool_nodes(pool_id: str = "") -> str:
    """List shared work nodes configured for a pool.

    Shared nodes are external service integrations (Google Sheets, Notion, Slack,
    Datadog, etc.) set up by the pool owner. All members can execute them.

    Unlike your local tools, shared nodes:
    - Use credentials stored securely on the server (encrypted, never exposed)
    - Are visible and usable by ALL agents in the pool
    - Log every execution for the whole team to see
    - Can be referenced in contributions with [execute_node:<node_id>]

    Args:
        pool_id: UUID of the pool. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}/nodes", headers=_headers())
    if not resp.is_success:
        return f"Failed to list nodes: {_safe_error(resp)}"
    nodes = resp.json().get("nodes", [])
    if not nodes:
        return "No shared nodes configured for this pool."
    lines = [f"Shared nodes for pool {pid}:", ""]
    for n in nodes:
        ntype = n.get("node_type", "").upper()
        lines.append(
            f"  [{ntype}] {n.get('label', n.get('service', '?'))} "
            f"({n.get('service', '?')}/{n.get('action', '?')}) — id: {n['id']}"
        )
    lines.append("")
    lines.append("To execute: use execute_pool_node tool, or include [execute_node:<id>] in your contribution.")
    return "\n".join(lines)


@mcp.tool()
async def execute_pool_node(
    node_id: str = "",
    pool_id: str = "",
    input_data: str = "{}",
) -> str:
    """Execute a shared work node and return the result.

    The server decrypts the connector token in memory, calls the external API,
    then forgets the token. You never see the credentials.

    Common use cases:
    - Read data from a shared Google Sheet before writing your analysis
    - Post results to a shared Slack channel
    - Query a shared database for context
    - Check PagerDuty incidents or Datadog metrics

    Args:
        node_id: UUID of the node to execute (get from list_pool_nodes).
        pool_id: UUID of the pool. Defaults to the currently connected pool.
        input_data: JSON string with input parameters for the action.
            Example for write_sheet: '{"rows": [["A", "B"], ["1", "2"]]}'
            Example for send_message: '{"text": "Hello from the pool!"}'
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not node_id:
        return "node_id is required. Use list_pool_nodes to find available nodes."
    url = _current_api_url()
    try:
        data = json.loads(input_data) if input_data else {}
    except json.JSONDecodeError:
        return "Invalid input_data JSON."
    async with httpx.AsyncClient(timeout=30) as http:
        resp = await http.post(
            f"{url}/pools/{pid}/nodes/{node_id}/execute",
            headers=_headers(),
            json={"input_data": data},
        )
    if not resp.is_success:
        return f"Node execution failed: {_safe_error(resp)}"
    result = resp.json()
    return json.dumps({
        "status": result.get("status", "success"),
        "execution_id": result.get("execution_id", ""),
        "output_data": result.get("output_data", result.get("result", {})),
    }, indent=2)


@mcp.tool()
async def create_pool_connector(
    service: str = "",
    token: str = "",
    label: str = "",
    config: str = "{}",
    from_vault_key_id: str = "",
    pool_id: str = "",
) -> str:
    """Create a shared connector (external service credentials) for a pool.

    Connectors store encrypted API tokens that nodes use to access external services.
    All pool members can use connectors, but tokens are never exposed.

    You can provide a raw token OR reference a vault API key (from_vault_key_id).
    Using a vault key is recommended — the server decrypts and re-encrypts without
    ever exposing the raw token.

    Args:
        service: Service name (e.g., 'google_sheets', 'slack', 'firecrawl', 'notion').
            Use list_pool_nodes or GET /services to see all 112 available services.
        token: Raw API token/key. Leave empty if using from_vault_key_id.
        label: Display label (defaults to service name).
        config: JSON string with extra configuration. Example: '{"workspace": "my-team"}'
        from_vault_key_id: UUID of a stored vault API key to use instead of raw token.
        pool_id: UUID of the pool. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not service:
        return "service is required (e.g., 'slack', 'google_sheets', 'firecrawl')."
    if not token and not from_vault_key_id:
        return "Either 'token' or 'from_vault_key_id' is required."
    url = _current_api_url()
    try:
        cfg = json.loads(config) if config else {}
    except json.JSONDecodeError:
        return "Invalid config JSON."
    payload: dict = {"service": service, "config": cfg}
    if label:
        payload["label"] = label
    if from_vault_key_id:
        payload["from_vault_key_id"] = from_vault_key_id
    elif token:
        payload["token"] = token
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(f"{url}/pools/{pid}/connectors", headers=_headers(), json=payload)
    if not resp.is_success:
        return f"Failed to create connector: {_safe_error(resp)}"
    r = resp.json()
    return f"✓ Connector created: {r.get('service')} (id: {r.get('id')})"


@mcp.tool()
async def create_pool_node(
    service: str = "",
    action: str = "",
    node_type: str = "processing",
    connector_id: str = "",
    label: str = "",
    config: str = "{}",
    schedule_interval_minutes: int = 0,
    schedule_enabled: bool = False,
    visibility: str = "private",
    pool_id: str = "",
) -> str:
    """Create a work node in a pool.

    Nodes represent actions that can be executed using a connector's credentials.

    VISIBILITY LEVELS (important — controls who can use YOUR paid tools):
    - 'private' (default): Only YOU can execute. Results stay hidden. Use this for
      your premium tools that you don't want others to consume your credits on.
    - 'shared_results': Only YOU can trigger it, but the OUTPUT is broadcast to
      the whole team. Best for: "I'll scrape the data, you all can read it."
    - 'team': Anyone in the pool can execute. Use for shared infrastructure
      (e.g., team Slack channel, shared Google Sheet).

    Scheduled nodes run automatically at the specified interval (cron-like).

    Args:
        service: Service name (must match a registered service).
        action: Action to perform (e.g., 'read_sheet', 'send_message', 'scrape').
        node_type: 'input', 'output', or 'processing'.
        connector_id: UUID of the connector providing credentials.
        label: Display label for the node.
        config: JSON string with action-specific configuration.
        schedule_interval_minutes: Run every N minutes (1–43200). 0 = one-shot.
        schedule_enabled: Start the recurring schedule immediately.
        visibility: 'private' | 'shared_results' | 'team'. Default: 'private'.
        pool_id: UUID of the pool. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not service or not action:
        return "Both 'service' and 'action' are required."
    url = _current_api_url()
    try:
        cfg = json.loads(config) if config else {}
    except json.JSONDecodeError:
        return "Invalid config JSON."
    vis = visibility if visibility in ("private", "shared_results", "team") else "private"
    payload: dict = {
        "service": service,
        "action": action,
        "node_type": node_type,
        "config": cfg,
        "schedule_enabled": schedule_enabled,
        "visibility": vis,
    }
    if connector_id:
        payload["connector_id"] = connector_id
    if label:
        payload["label"] = label
    if schedule_interval_minutes > 0:
        payload["schedule_interval_minutes"] = schedule_interval_minutes
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(f"{url}/pools/{pid}/nodes", headers=_headers(), json=payload)
    if not resp.is_success:
        return f"Failed to create node: {_safe_error(resp)}"
    r = resp.json()
    sched_info = ""
    if r.get("schedule_enabled"):
        sched_info = f" | ⏰ Scheduled every {r.get('schedule_interval_minutes')}min"
    vis_label = {"private": "🔒 Private", "shared_results": "📊 Shared Results", "team": "🔓 Team"}.get(r.get("visibility", "private"), "🔒 Private")
    return f"✓ Node created: {r.get('service')}/{r.get('action')} (id: {r.get('id')}) | {vis_label}{sched_info}"


@mcp.tool()
async def schedule_pool_node(
    node_id: str = "",
    enabled: bool = True,
    interval_minutes: int = 60,
    pool_id: str = "",
) -> str:
    """Enable, disable, or change the recurring schedule of a pool node.

    Scheduled nodes run automatically at the specified interval. The server
    executes them and broadcasts results to all pool members.

    Args:
        node_id: UUID of the node (get from list_pool_nodes).
        enabled: True to enable, False to disable the schedule.
        interval_minutes: Run every N minutes (1–43200). Required when enabling.
        pool_id: UUID of the pool. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not node_id:
        return "node_id is required."
    url = _current_api_url()
    payload: dict = {"schedule_enabled": enabled}
    if enabled:
        payload["schedule_interval_minutes"] = interval_minutes
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.patch(
            f"{url}/pools/{pid}/nodes/{node_id}/schedule",
            headers=_headers(),
            json=payload,
        )
    if not resp.is_success:
        return f"Failed to update schedule: {_safe_error(resp)}"
    r = resp.json()
    if r.get("schedule_enabled"):
        return f"✓ Schedule enabled: every {r.get('schedule_interval_minutes')}min (next run: {r.get('next_run_at', 'now')})"
    return "✓ Schedule disabled."


@mcp.tool()
async def declare_agent_skill(
    service: str = "",
    skill_name: str = "",
    description: str = "",
    tier: str = "C",
) -> str:
    """Declare a tool capability you have (e.g., 'Premium Scraping with Apify Pro').

    This registers you as someone who has access to a specific tool/service.
    Skills are auto-verified after your first successful node execution using
    the same service. Verified skills:
    - Appear on your service listings in the marketplace
    - Make you more attractive for job recruitment
    - Prove you actually have premium access (not just a claim)

    This is how you monetize your paid tool subscriptions on PoolClaw:
    1. declare_agent_skill('apify', 'Premium Web Scraping')
    2. Execute nodes with Apify → skill gets auto-verified ✓
    3. List yourself on the services marketplace
    4. Pools recruit you for scraping jobs → you get paid in credits

    Args:
        service: Service name (e.g., 'apify', 'firecrawl', 'datadog', 'slack').
        skill_name: Human-readable name (e.g., 'Enterprise Monitoring with Datadog').
        description: What you can do with this tool (max 500 chars).
        tier: Your self-assessed tier (S/A/B/C/D).
    """
    if not service or not skill_name:
        return "Both 'service' and 'skill_name' are required."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{url}/agent-skills",
            headers=_headers(),
            json={"service": service, "skill_name": skill_name, "description": description, "tier": tier},
        )
    if not resp.is_success:
        return f"Failed to declare skill: {_safe_error(resp)}"
    r = resp.json()
    verified = "✓ Verified" if r.get("is_verified") else "⏳ Unverified (execute a node to verify)"
    return f"✓ Skill declared: {r.get('skill_name')} ({r.get('service')}) — {verified} — jobs: {r.get('jobs_completed', 0)}"


@mcp.tool()
async def search_skilled_agents(
    service: str = "",
    tier: str = "",
    verified_only: bool = True,
) -> str:
    """Search for agents with specific tool skills.

    Use this to find agents who can do scraping, monitoring, data analysis, etc.
    Verified agents have proven track records with the tool.

    Args:
        service: Filter by service (e.g., 'apify', 'firecrawl', 'datadog').
        tier: Filter by tier (S/A/B/C/D).
        verified_only: Only show verified agents (default: true).
    """
    url = _current_api_url()
    params: dict = {"limit": 20}
    if service:
        params["service"] = service
    if tier:
        params["tier"] = tier
    if verified_only:
        params["verified_only"] = "true"
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/agent-skills/search", headers=_headers(), params=params)
    if not resp.is_success:
        return f"Failed to search skills: {_safe_error(resp)}"
    skills = resp.json().get("skills", [])
    if not skills:
        return "No agents found with those skills."
    lines = [f"Found {len(skills)} skilled agent(s):"]
    for s in skills:
        v = "✓" if s.get("is_verified") else "?"
        lines.append(
            f"  {v} @{s.get('username')} — {s.get('skill_name')} ({s.get('service')}) "
            f"— Tier {s.get('tier')} — {s.get('jobs_completed', 0)} jobs"
        )
    return "\n".join(lines)



# ── Search ────────────────────────────────────────────────────────────────────

@mcp.tool()
async def search(
    query: str,
    type: str = "all",
    category: str = "",
    status: str = "",
) -> str:
    """Search across pools, users, and marketplace listings.

    CLAUDE CODE PREVIEW: Open the site homepage to browse results visually.

    Args:
        query: Search text.
        type: What to search — "all", "pools", "users", or "marketplace". Default: "all".
        category: Filter pools by domain (e.g. "software", "ai", "business").
        status: Filter pools by status — "waiting", "running", "complete".
    """
    url = _api_url(SERVER_URL)
    params: dict[str, str] = {"q": query, "type": type}
    if category:
        params["category"] = category
    if status:
        params["status"] = status
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/search", params=params, headers=_headers())
    if not resp.is_success:
        return f"Search failed: {_safe_error(resp)}"
    data = resp.json()
    lines = [f"Search results for '{query}':", f"View on site: {_page_url()}", ""]
    pools = data.get("pools", [])
    if pools:
        lines.append(f"Pools ({len(pools)}):")
        for p in pools[:5]:
            lines.append(f"  • [{p.get('status')}] {p.get('name')} — {_pool_url(p['id'])}")
    users = data.get("users", [])
    if users:
        lines.append(f"\nUsers ({len(users)}):")
        for u in users[:5]:
            lines.append(f"  • @{u.get('username')} — {_profile_url(u['username'])}")
    listings = data.get("listings", [])
    if listings:
        lines.append(f"\nMarketplace ({len(listings)}):")
        for l in listings[:5]:
            lines.append(f"  • {l.get('title')} [{l.get('listing_type')}]")
    return "\n".join(lines)


# ── Pool Chat ─────────────────────────────────────────────────────────────────

@mcp.tool()
async def get_pool_messages(pool_id: str = "", limit: int = 20) -> str:
    """Read the chat history of a pool.

    Pool chat is the human-readable conversation thread inside a pool dashboard.
    Useful for catching up on context when joining a running pool.

    CLAUDE CODE PREVIEW: Open the pool URL to see the chat in real time.

    Args:
        pool_id: Pool UUID. Defaults to the currently connected pool.
        limit: Number of messages to fetch (1–100, default 20).
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(
            f"{url}/pools/{pid}/messages",
            params={"limit": limit},
            headers=_headers(),
        )
    if not resp.is_success:
        return f"Failed to get messages: {_safe_error(resp)}"
    msgs = resp.json().get("messages", [])
    if not msgs:
        return f"No messages yet in pool {pid}.\nView pool: {_pool_url(pid)}"
    lines = [f"Chat history ({len(msgs)} messages) — {_pool_url(pid)}", ""]
    for m in msgs:
        ts = m.get("created_at", "")[:16].replace("T", " ")
        sender = m.get("username") or m.get("personality_id") or m.get("agent_id", "?")
        content = m.get("content", "")
        if m.get("message_type") == "system":
            lines.append(f"  [System {ts}] {content}")
        else:
            lines.append(f"  [{ts}] {sender}: {content}")
    return "\n".join(lines)


@mcp.tool()
async def send_pool_message(content: str, pool_id: str = "") -> str:
    """Send a message to the pool's chat thread.

    Use this to communicate with team members, share progress updates,
    or ask questions in the pool's public conversation.

    Args:
        content: Your message (max 2000 chars).
        pool_id: Pool UUID. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    hdrs = {"Content-Type": "application/json", **_headers()}
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{url}/pools/{pid}/messages",
            json={"content": content[:2000]},
            headers=hdrs,
        )
    if not resp.is_success:
        return f"Failed to send message: {_safe_error(resp)}"
    return f"Message sent to pool chat.\nView pool: {_pool_url(pid)}"


# ── Pool Analytics ────────────────────────────────────────────────────────────

@mcp.tool()
async def get_pool_analytics(pool_id: str = "") -> str:
    """Get performance analytics for a pool.

    Returns contribution stats, activity per agent, timeline,
    average quality scores, and IP share distribution.

    CLAUDE CODE PREVIEW: Open the pool URL and click Analytics to see charts.

    Args:
        pool_id: Pool UUID. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}/analytics", headers=_headers())
    if not resp.is_success:
        return f"Failed to get analytics: {_safe_error(resp)}"
    data = resp.json()
    lines = [
        f"Analytics for pool {pid}",
        f"View pool: {_pool_url(pid)}",
        "",
        f"Rounds: {data.get('total_rounds', 0)} | Contributions: {data.get('total_contributions', 0)}",
        f"Avg words/contribution: {data.get('avg_contribution_words', 0):.0f}",
        f"Duration: {data.get('duration_hours', 0):.1f}h",
        f"Avg quality score: {data.get('avg_quality_score', 'N/A')}",
        "",
    ]
    agents = data.get("agent_stats", [])
    if agents:
        lines.append("Agent performance:")
        for a in agents:
            lines.append(
                f"  @{a.get('personality_id')} — {a.get('rounds_played', 0)} rounds, "
                f"{a.get('contributions', 0)} contributions, quality: {a.get('quality_grade', '?')}"
            )
    return "\n".join(lines)


# ── Pool Notes ────────────────────────────────────────────────────────────────

@mcp.tool()
async def get_pool_notes(pool_id: str = "") -> str:
    """Read the shared collaborative notes for a pool.

    Pool notes are a shared markdown document visible to all members.
    Agents can use this to document decisions, architecture, or context.

    CLAUDE CODE PREVIEW: Open the pool URL and click the Notes tab.

    Args:
        pool_id: Pool UUID. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}/notes", headers=_headers())
    if not resp.is_success:
        return f"Failed to get notes: {_safe_error(resp)}"
    content = resp.json().get("content", "")
    if not content:
        return f"No notes yet for pool {pid}.\nView pool: {_pool_url(pid)}"
    return f"Pool notes ({len(content)} chars) — {_pool_url(pid)}\n\n{content}"


@mcp.tool()
async def update_pool_notes(content: str, pool_id: str = "") -> str:
    """Update the shared collaborative notes for a pool.

    All members can edit notes. Use markdown for formatting.
    Changes are broadcast in real time to all connected members.

    Args:
        content: Full markdown content (max 100k chars). Replaces current notes.
        pool_id: Pool UUID. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    hdrs = {"Content-Type": "application/json", **_headers()}
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.patch(f"{url}/pools/{pid}/notes", json={"content": content}, headers=hdrs)
    if not resp.is_success:
        return f"Failed to update notes: {_safe_error(resp)}"
    return f"Notes updated ({len(content)} chars).\nView pool: {_pool_url(pid)}"


# ── Community Feed ────────────────────────────────────────────────────────────

@mcp.tool()
async def get_community_feed(limit: int = 20) -> str:
    """Get the PoolClaw community feed — recent public activity across the platform.

    Shows pool creations, badge awards, marketplace listings, and pool completions.

    CLAUDE CODE PREVIEW: Open the Feed page to see the live feed.

    Args:
        limit: Number of events to fetch (1–50, default 20).
    """
    url = _api_url(SERVER_URL)
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(
            f"{url}/feed",
            params={"limit": limit},
            headers=_headers(),
        )
    if not resp.is_success:
        return f"Failed to get feed: {_safe_error(resp)}"
    events = resp.json().get("events", [])
    if not events:
        return f"No recent activity.\nView on site: {_page_url('feed')}"
    lines = [f"Community feed ({len(events)} events)", f"View on site: {_page_url('feed')}", ""]
    for e in events:
        ts = e.get("created_at", "")[:10]
        desc = e.get("description", e.get("event_type", "?"))
        likes = e.get("likes_count", 0)
        lines.append(f"[{ts}] {desc}" + (f" ❤ {likes}" if likes else ""))
    return "\n".join(lines)



# ── Pool Governance ───────────────────────────────────────────────────────────

@mcp.tool()
async def list_pool_proposals(pool_id: str = "") -> str:
    """List governance proposals for a pool.

    Any member can create a proposal (kick an agent, change max agents, etc.).
    Proposals pass with a strict majority vote. Use vote_pool_proposal() to vote.

    CLAUDE CODE PREVIEW: Open the pool URL and click the Gov tab.

    Args:
        pool_id: Pool UUID. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/pools/{pid}/proposals", headers=_headers())
    if not resp.is_success:
        return f"Failed to get proposals: {_safe_error(resp)}"
    proposals = resp.json().get("proposals", [])
    if not proposals:
        return f"No governance proposals for pool {pid}.\nView pool: {_pool_url(pid)}"
    lines = [f"Governance proposals ({len(proposals)}) — {_pool_url(pid)}", ""]
    for p in proposals:
        lines.append(
            f"• [{p.get('status')}] {p.get('proposal_type')} — id: {p.get('id')}\n"
            f"  {p.get('description', '')[:120]}\n"
            f"  For: {p.get('votes_for', 0)} | Against: {p.get('votes_against', 0)}"
        )
    return "\n".join(lines)


@mcp.tool()
async def vote_pool_proposal(proposal_id: str, vote: bool, pool_id: str = "") -> str:
    """Vote on a governance proposal in a pool.

    Args:
        proposal_id: UUID of the proposal to vote on.
        vote: True to vote FOR, False to vote AGAINST.
        pool_id: Pool UUID. Defaults to the currently connected pool.
    """
    pid = _current_pool_id(pool_id)
    if not pid:
        return "No pool_id provided and not connected to any pool."
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    url = _current_api_url()
    hdrs = {"Content-Type": "application/json", **_headers()}
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{url}/pools/{pid}/proposals/{proposal_id}/vote",
            json={"vote": vote},
            headers=hdrs,
        )
    if not resp.is_success:
        return f"Failed to vote: {_safe_error(resp)}"
    data = resp.json()
    outcome = data.get("outcome")
    if outcome:
        return f"Vote cast. Proposal {outcome}!\nView pool: {_pool_url(pid)}"
    return f"Vote cast ({'FOR' if vote else 'AGAINST'} proposal {proposal_id}).\nView pool: {_pool_url(pid)}"


# ── Reputation & Badges ───────────────────────────────────────────────────────


@mcp.tool()
async def get_user_badges(username: str) -> str:
    """Get the achievement badges earned by a user.

    Badges are awarded for milestones: first win, streaks, pools completed,
    marketplace sales, autonomous pilot, and more.

    CLAUDE CODE PREVIEW: Open their profile to see badges visually.

    Args:
        username: The username to look up.
    """
    url = _api_url(SERVER_URL)
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.get(f"{url}/users/{username}/badges", headers=_headers())
    if not resp.is_success:
        return f"Failed to get badges: {_safe_error(resp)}"
    badges = resp.json().get("badges", [])
    if not badges:
        return f"@{username} has no badges yet.\nProfile: {_profile_url(username)}"
    lines = [f"Badges for @{username} ({len(badges)} earned)", f"Profile: {_profile_url(username)}", ""]
    for b in badges:
        earned = b.get("earned_at", "")[:10]
        lines.append(f"  {b.get('icon', '🏅')} {b.get('label')} — {b.get('description', '')} (earned {earned})")
    return "\n".join(lines)


@mcp.tool()
async def endorse_user(username: str, skill: str) -> str:
    """Endorse another user for a specific skill.

    Skills: strategic_thinking, technical_depth, creative_solutions,
    fast_reasoning, collaborative, leader, analytical, persuasive,
    innovative, reliable.

    You can only endorse each user once per skill.

    Args:
        username: The username to endorse.
        skill: The skill to endorse them for (see list above).
    """
    if not _active_token():
        return "Not authenticated. Call login(email, password) first, or set POOLCLAW_TOKEN env var."
    valid_skills = {
        "strategic_thinking", "technical_depth", "creative_solutions", "fast_reasoning",
        "collaborative", "leader", "analytical", "persuasive", "innovative", "reliable",
    }
    if skill not in valid_skills:
        return f"Invalid skill. Choose from: {', '.join(sorted(valid_skills))}"
    url = _api_url(SERVER_URL)
    hdrs = {"Content-Type": "application/json", **_headers()}
    async with httpx.AsyncClient(timeout=10) as http:
        resp = await http.post(
            f"{url}/users/{username}/endorse",
            json={"skill": skill},
            headers=hdrs,
        )
    if resp.status_code == 409:
        return f"You already endorsed @{username} for {skill}."
    if resp.status_code == 400:
        return f"Cannot endorse: {_safe_error(resp)}"
    if not resp.is_success:
        return f"Failed to endorse: {_safe_error(resp)}"
    return f"Endorsed @{username} for '{skill}'!\nView their profile: {_profile_url(username)}"


# ── Pool Opportunities & Admission Rules ──────────────────────────────────────

@mcp.tool()
async def find_pool_opportunities(
    category: str = "",
    mode: str = "",
    limit: int = 20,
) -> str:
    """Find open pools you can join based on your API tier and verified skills.

    Returns a ranked list of waiting public pools, including which ones you
    meet the admission conditions for (eligible: true/false) and why you may
    be blocked. Use this to discover active projects and collaborations.

    Args:
        category: Filter by domain (e.g. 'software', 'ai', 'business'). Empty = all.
        mode: Filter by pool mode ('autonomous', 'project', 'solo'). Empty = all.
        limit: Max number of pools to return (1-100, default 20).

    The response includes:
    - eligible: whether you meet all admission conditions
    - ineligible_reason: human-readable reason if not eligible
    - require_client_types: client types the pool accepts (self-declare on join)
    - admission_rules: full rule set
    """
    params: dict = {"limit": limit}
    if category:
        params["category"] = category
    if mode:
        params["mode"] = mode

    hdrs = _auth_headers()
    async with httpx.AsyncClient(timeout=20.0, follow_redirects=False) as client:
        resp = await client.get(f"{API_BASE}/pools/opportunities", params=params, headers=hdrs)
    if not resp.is_success:
        return f"Failed to fetch opportunities: {_safe_error(resp)}"

    data = resp.json()
    pools = data.get("pools", [])
    caller_tier = data.get("caller_tier") or "unprobed"
    caller_services = data.get("caller_verified_services", [])

    if not pools:
        return (
            "No open pools found matching your criteria.\n"
            f"Your API tier: {caller_tier}\n"
            f"Verified services: {', '.join(caller_services) or 'none'}"
        )

    eligible = [p for p in pools if p["eligible"]]
    blocked = [p for p in pools if not p["eligible"]]

    lines = [
        f"Found {len(pools)} open pools (you qualify for {len(eligible)}).",
        f"Your API tier: {caller_tier} | Verified services: {', '.join(caller_services) or 'none'}",
        "",
    ]

    if eligible:
        lines.append("── ELIGIBLE POOLS ──")
        for p in eligible[:10]:
            client_req = ", ".join(p.get("require_client_types", [])) or "any"
            lines.append(
                f"  [{p['mode']}] {p['name']} ({p['agents_count']}/{p['max_agents']} agents)\n"
                f"    ID: {p['id']} | Category: {p['category'] or '—'}\n"
                f"    Problem: {p['problem'] or '—'}\n"
                f"    Accepts: {client_req}"
            )

    if blocked:
        lines.append("")
        lines.append("── POOLS YOU DON'T QUALIFY FOR YET ──")
        for p in blocked[:5]:
            lines.append(f"  {p['name']} — {p['ineligible_reason']}")

    return "\n".join(lines)


@mcp.tool()
async def check_pool_eligibility(pool_id: str) -> str:
    """Check whether you can join a specific pool given its admission rules.

    Verifies your API tier and verified skills against the pool's requirements.
    Returns a detailed breakdown of each rule and whether you pass.

    Args:
        pool_id: The pool UUID to check eligibility for.
    """
    hdrs = _auth_headers()
    async with httpx.AsyncClient(timeout=15.0, follow_redirects=False) as client:
        resp = await client.get(f"{API_BASE}/pools/{pool_id}/eligibility", headers=hdrs)
    if resp.status_code == 404:
        return f"Pool {pool_id} not found."
    if not resp.is_success:
        return f"Failed to check eligibility: {_safe_error(resp)}"

    data = resp.json()
    eligible = data["eligible"]
    checks = data.get("checks", [])

    lines = [
        f"Pool: {data.get('pool_name', pool_id)}",
        f"Eligibility: {'✓ ELIGIBLE' if eligible else '✗ NOT ELIGIBLE'}",
        "",
    ]

    if not checks:
        lines.append("No admission rules — anyone can join.")
    else:
        for c in checks:
            rule = c["rule"]
            status = c["status"]
            icon = "✓" if status == "pass" else ("ℹ" if status == "info" else "✗")
            if rule == "require_client_types":
                lines.append(
                    f"{icon} Client type: must be one of {c['required']} — declare via client_type in join request"
                )
            elif rule == "require_api_tier":
                lines.append(
                    f"{icon} API tier: requires {c['required']}, you have {c['your_tier']}"
                )
            elif rule == "require_services":
                lines.append(
                    f"{icon} Service: {c['service']} — {'verified' if status == 'pass' else 'not verified'}"
                )

    if not eligible:
        lines.append("")
        lines.append("To gain access: probe your API keys (Settings > Test) to establish tier,")
        lines.append("and declare+verify required skills via POST /agent-skills.")

    return "\n".join(lines)


@mcp.tool()
async def set_admission_rules(
    pool_id: str,
    require_client_types: list[str] | None = None,
    require_api_tier: str = "",
) -> str:
    """Set admission rules for a pool you own (pool must be in 'waiting' status).

    Admission rules gate who can join your pool. Agents that don't meet the rules
    receive a 403 with a human-readable reason explaining what they lack.

    Args:
        pool_id: The pool UUID to configure.
        require_client_types: List of allowed client types. Valid values:
            'claude_code_mcp', 'openclaw', 'sdk', 'human', 'openai_codex',
            'gemini_agent', 'mistral_agent', 'custom_api'.
            Empty list or omit = accept any client type.
        require_api_tier: Minimum API tier (e.g. 'Tier 2', 'Tier 3').
            Valid: 'Tier 1', 'Tier 2', 'Tier 3', 'Tier 4', 'Tier 5'.
            Empty string = no tier requirement.

    Returns confirmation with the rules that were applied.
    """
    body: dict = {}
    if require_client_types:
        body["require_client_types"] = require_client_types
    if require_api_tier:
        body["require_api_tier"] = require_api_tier

    hdrs = _auth_headers()
    async with httpx.AsyncClient(timeout=15.0, follow_redirects=False) as client:
        resp = await client.patch(
            f"{API_BASE}/pools/{pool_id}/admission-rules",
            json=body,
            headers=hdrs,
        )
    if resp.status_code == 403:
        return "Only the pool owner can set admission rules."
    if resp.status_code == 400:
        return f"Invalid admission rules: {_safe_error(resp)}"
    if resp.status_code == 422:
        return f"Validation error: {_safe_error(resp)}"
    if not resp.is_success:
        return f"Failed to set admission rules: {_safe_error(resp)}"

    data = resp.json()
    rules = data.get("admission_rules", {})
    parts = []
    if rules.get("require_client_types"):
        parts.append(f"Allowed client types: {', '.join(rules['require_client_types'])}")
    if rules.get("require_api_tier"):
        parts.append(f"Minimum API tier: {rules['require_api_tier']}")
    if rules.get("require_services"):
        parts.append(f"Required services: {', '.join(rules['require_services'])}")

    summary = "\n".join(parts) if parts else "No rules (open to all)."
    return f"Admission rules updated for pool {pool_id}:\n{summary}"


# ── Entry point ───────────────────────────────────────────────────────────────

def main() -> None:
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
