mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 07:20:44 +08:00
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
161 lines
7.7 KiB
Python
161 lines
7.7 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
from urllib.parse import urlencode
|
|
|
|
import httpx
|
|
|
|
|
|
def _strip_trailing_slash(url: str) -> str:
|
|
return str(url or "").strip().rstrip("/")
|
|
|
|
|
|
def _join_url(base: str, path: str) -> str:
|
|
b = _strip_trailing_slash(base)
|
|
p = str(path or "").strip()
|
|
if not p:
|
|
return b
|
|
if not p.startswith("/"):
|
|
p = "/" + p
|
|
return b + p
|
|
|
|
|
|
def _safe_get_json(url: str, *, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
|
|
try:
|
|
with httpx.Client(timeout=8.0, follow_redirects=True) as c:
|
|
r = c.get(url, params=params or {}, headers=headers or {})
|
|
if r.status_code != 200:
|
|
return {}
|
|
obj = r.json()
|
|
return obj if isinstance(obj, dict) else {}
|
|
except Exception:
|
|
return {}
|
|
|
|
|
|
def _safe_get_json_list(url: str, *, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None) -> list[dict[str, Any]]:
|
|
try:
|
|
with httpx.Client(timeout=8.0, follow_redirects=True) as c:
|
|
r = c.get(url, params=params or {}, headers=headers or {})
|
|
if r.status_code != 200:
|
|
return []
|
|
obj = r.json()
|
|
if isinstance(obj, list):
|
|
return [x for x in obj if isinstance(x, dict)]
|
|
if isinstance(obj, dict) and isinstance(obj.get("items"), list):
|
|
return [x for x in obj.get("items") if isinstance(x, dict)]
|
|
if isinstance(obj, dict) and isinstance(obj.get("results"), list):
|
|
return [x for x in obj.get("results") if isinstance(x, dict)]
|
|
return []
|
|
except Exception:
|
|
return []
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ClawHubConfig:
|
|
site_base_url: str = "https://clawhub.ai"
|
|
registry_base_url: str | None = None
|
|
token: str | None = None
|
|
api_base_path: str = "/api/v1"
|
|
|
|
|
|
def load_clawhub_config() -> ClawHubConfig:
|
|
site = str(os.getenv("AIA_CLAWHUB_SITE") or os.getenv("CLAWHUB_SITE") or "https://clawhub.ai").strip() or "https://clawhub.ai"
|
|
registry = str(os.getenv("AIA_CLAWHUB_REGISTRY") or os.getenv("CLAWHUB_REGISTRY") or "").strip() or None
|
|
token = str(os.getenv("AIA_CLAWHUB_TOKEN") or os.getenv("CLAWHUB_TOKEN") or "").strip() or None
|
|
api_base = str(os.getenv("AIA_CLAWHUB_API_BASE") or "").strip() or "/api/v1"
|
|
if not api_base.startswith("/"):
|
|
api_base = "/" + api_base
|
|
return ClawHubConfig(site_base_url=site, registry_base_url=registry, token=token, api_base_path=api_base)
|
|
|
|
|
|
def discover_registry_base_url(cfg: ClawHubConfig) -> str:
|
|
if cfg.registry_base_url:
|
|
return _strip_trailing_slash(cfg.registry_base_url)
|
|
wk = _safe_get_json(_join_url(cfg.site_base_url, "/.well-known/clawhub.json"))
|
|
api_base = str(wk.get("apiBase") or "").strip()
|
|
if api_base and api_base.startswith("/"):
|
|
return _strip_trailing_slash(cfg.site_base_url)
|
|
return _strip_trailing_slash(cfg.site_base_url)
|
|
|
|
|
|
def _auth_headers(cfg: ClawHubConfig) -> dict[str, str]:
|
|
tok = str(cfg.token or "").strip()
|
|
if not tok:
|
|
return {}
|
|
return {"Authorization": f"Bearer {tok}"}
|
|
|
|
|
|
def build_download_url(*, registry_base_url: str, api_base_path: str, slug: str, version: str) -> str:
|
|
q = urlencode({"slug": slug, "version": version})
|
|
return _join_url(registry_base_url, f"{api_base_path.rstrip('/')}/download?{q}")
|
|
|
|
|
|
def search_skills(query: str, *, limit: int = 20, cfg: ClawHubConfig | None = None) -> list[dict[str, Any]]:
|
|
cfg = cfg or load_clawhub_config()
|
|
registry = discover_registry_base_url(cfg)
|
|
api = cfg.api_base_path.rstrip("/")
|
|
q = str(query or "").strip()
|
|
lim = max(1, min(int(limit or 20), 200))
|
|
items = _safe_get_json_list(_join_url(registry, f"{api}/search"), params={"q": q, "limit": lim}, headers=_auth_headers(cfg))
|
|
out: list[dict[str, Any]] = []
|
|
for it in items:
|
|
slug = str(it.get("slug") or it.get("name") or "").strip()
|
|
if not slug:
|
|
continue
|
|
version = str(it.get("version") or it.get("latestVersion") or it.get("latest") or "").strip()
|
|
archive_url = build_download_url(registry_base_url=registry, api_base_path=api, slug=slug, version=version) if version else ""
|
|
out.append({"source": "clawhub", "slug": slug, "name": str(it.get("displayName") or it.get("name") or slug), "description": str(it.get("summary") or it.get("description") or ""), "version": version, "owner": str(it.get("owner") or it.get("ownerHandle") or ""), "updatedAt": str(it.get("updatedAt") or ""), "downloads": int(it.get("downloads") or (it.get("stats") or {}).get("downloads") or 0) if isinstance(it.get("stats"), dict) else int(it.get("downloads") or 0), "stars": int(it.get("stars") or (it.get("stats") or {}).get("stars") or 0) if isinstance(it.get("stats"), dict) else int(it.get("stars") or 0), "homepage": str(it.get("homepage") or it.get("url") or ""), "archiveUrl": archive_url, "raw": it})
|
|
return out
|
|
|
|
|
|
def get_skill_detail(slug: str, *, cfg: ClawHubConfig | None = None) -> dict[str, Any]:
|
|
cfg = cfg or load_clawhub_config()
|
|
registry = discover_registry_base_url(cfg)
|
|
api = cfg.api_base_path.rstrip("/")
|
|
s = str(slug or "").strip()
|
|
if not s:
|
|
return {}
|
|
blob = _safe_get_json(_join_url(registry, f"{api}/skills/{s}"), headers=_auth_headers(cfg))
|
|
if not blob:
|
|
return {"slug": s}
|
|
core = blob.get("skill") if isinstance(blob.get("skill"), dict) else blob
|
|
core = core if isinstance(core, dict) else {}
|
|
stats = core.get("stats") if isinstance(core.get("stats"), dict) else {}
|
|
versions: list[dict[str, Any]] = []
|
|
versions_raw = blob.get("versions")
|
|
if not isinstance(versions_raw, list):
|
|
versions_raw = core.get("versions")
|
|
if isinstance(versions_raw, list):
|
|
for v in versions_raw:
|
|
if not isinstance(v, dict):
|
|
continue
|
|
ver = str(v.get("version") or v.get("tag") or "").strip()
|
|
if not ver:
|
|
continue
|
|
versions.append({"version": ver, "changelog": str(v.get("changelog") or ""), "createdAt": str(v.get("createdAt") or ""), "archiveUrl": build_download_url(registry_base_url=registry, api_base_path=api, slug=s, version=ver), "raw": v})
|
|
latest_version_raw = blob.get("latestVersion")
|
|
if latest_version_raw is None:
|
|
latest_version_raw = core.get("latestVersion") or (core.get("tags") if isinstance(core.get("tags"), dict) else {}).get("latest")
|
|
latest_version = str(latest_version_raw.get("version") or "").strip() if isinstance(latest_version_raw, dict) else str(latest_version_raw or blob.get("latest") or "").strip()
|
|
if not latest_version and versions:
|
|
latest_version = str(versions[0].get("version") or "")
|
|
return {
|
|
"source": "clawhub",
|
|
"slug": str(core.get("slug") or s),
|
|
"name": str(core.get("displayName") or core.get("name") or s),
|
|
"description": str(core.get("summary") or core.get("description") or ""),
|
|
"owner": str(core.get("ownerHandle") or (core.get("owner") or {}).get("handle") or "") if isinstance(core.get("owner"), dict) else str(core.get("ownerHandle") or ""),
|
|
"updatedAt": str(core.get("updatedAt") or ""),
|
|
"homepage": str(core.get("homepage") or core.get("url") or ""),
|
|
"latestVersion": latest_version,
|
|
"archiveUrl": build_download_url(registry_base_url=registry, api_base_path=api, slug=s, version=latest_version) if latest_version else "",
|
|
"downloads": int(core.get("downloads") or stats.get("downloads") or 0),
|
|
"stars": int(core.get("stars") or stats.get("stars") or 0),
|
|
"versions": versions,
|
|
"raw": blob,
|
|
}
|
|
|
|
|
|
__all__ = ["ClawHubConfig", "load_clawhub_config", "discover_registry_base_url", "build_download_url", "search_skills", "get_skill_detail"]
|