"""CLI Apps helpers for the WebUI HTTP and message surfaces.""" from __future__ import annotations import re from typing import Any from nanobot.apps.cli import CliAppError, CliAppManager, CliAppsRuntimeConfig from nanobot.config.loader import load_config QueryParams = dict[str, list[str]] _CLI_APP_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$", re.IGNORECASE) _CLI_APP_ATTACHMENT_KEYS = ( "name", "display_name", "category", "entry_point", "logo_url", "brand_color", ) def _clip_ws_string(value: Any, limit: int = 240) -> str | None: if not isinstance(value, str): return None text = value.strip() if not text: return None return text[:limit] def normalize_cli_app_mentions(raw: Any) -> list[dict[str, str]]: """Sanitize structured CLI app mentions sent by the WebUI.""" if not isinstance(raw, list): return [] out: list[dict[str, str]] = [] seen: set[str] = set() for item in raw[:8]: if not isinstance(item, dict): continue name = _clip_ws_string(item.get("name"), 64) if not name or _CLI_APP_NAME_RE.match(name) is None: continue key = name.lower() if key in seen: continue seen.add(key) row: dict[str, str] = {"name": key} for field in _CLI_APP_ATTACHMENT_KEYS[1:]: value = _clip_ws_string(item.get(field), 512 if field == "logo_url" else 160) if value: row[field] = value out.append(row) return out def _query_first(query: QueryParams, key: str) -> str | None: values = query.get(key) return values[0] if values else None def _manager() -> CliAppManager: config = load_config() cli_cfg = config.tools.cli_apps return CliAppManager( workspace=config.workspace_path, runtime=CliAppsRuntimeConfig( install_timeout=cli_cfg.install_timeout, run_timeout=cli_cfg.run_timeout, catalog_ttl_seconds=cli_cfg.catalog_ttl_seconds, ), ) def cli_apps_payload() -> dict[str, Any]: return _manager().payload() def cli_apps_action(action: str, query: QueryParams) -> dict[str, Any]: name = (_query_first(query, "name") or "").strip() if not name: raise CliAppError("missing CLI app name") manager = _manager() if action == "install": return manager.install(name) if action == "update": return manager.update(name) if action == "uninstall": return manager.uninstall(name) if action == "test": return manager.test(name) raise CliAppError(f"unknown CLI app action '{action}'", status=404)