mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-14 06:43:53 +00:00
* refactor(cli): load bundled apps from catalog * feat(plugins): unify CLI and MCP settings * feat(plugins): add settings category filter * style(plugins): refine settings catalog * refactor(cli): load nanobot apps from repo catalog * feat(store): add capability store entry * feat(apps): rename capability store * fix(apps): verify clean app removal * fix(apps): keep main sidebar on apps view * feat(apps): add shared app manifest protocol * fix(apps): dismiss app status message * refactor(apps): move CLI adapter under apps * refactor(apps): drop legacy cli apps package
94 lines
2.6 KiB
Python
94 lines
2.6 KiB
Python
"""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)
|