mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-18 18:10:37 +08:00
- add group metadata driven worker startup for shared env plans - track per-plugin handler and capability ownership inside grouped workers - update runtime and smoke tests for grouped worker session behavior
104 lines
3.0 KiB
Python
104 lines
3.0 KiB
Python
"""AstrBot SDK 的命令行入口。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import sys
|
|
from collections.abc import Coroutine
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import click
|
|
from loguru import logger
|
|
|
|
from .runtime.bootstrap import run_plugin_worker, run_supervisor, run_websocket_server
|
|
|
|
|
|
def setup_logger(verbose: bool = False) -> None:
|
|
"""初始化 CLI 使用的日志配置。"""
|
|
logger.remove()
|
|
logger.add(
|
|
sys.stderr,
|
|
format="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <level>{message}</level>",
|
|
level="DEBUG" if verbose else "INFO",
|
|
colorize=True,
|
|
)
|
|
|
|
|
|
def _run_async_entrypoint(
|
|
entrypoint: Coroutine[Any, Any, object],
|
|
*,
|
|
log_message: str,
|
|
log_level: str = "info",
|
|
) -> None:
|
|
log_method = getattr(logger, log_level)
|
|
log_method(log_message)
|
|
asyncio.run(entrypoint)
|
|
|
|
|
|
@click.group()
|
|
@click.option("-v", "--verbose", is_flag=True, help="Enable verbose output")
|
|
@click.pass_context
|
|
def cli(ctx, verbose: bool) -> None:
|
|
"""AstrBot SDK CLI。"""
|
|
ctx.ensure_object(dict)
|
|
ctx.obj["verbose"] = verbose
|
|
setup_logger(verbose)
|
|
|
|
|
|
@cli.command()
|
|
@click.option(
|
|
"--plugins-dir",
|
|
default="plugins",
|
|
type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
|
|
help="Directory containing plugin folders",
|
|
)
|
|
def run(plugins_dir: Path) -> None:
|
|
"""Start the plugin supervisor over stdio."""
|
|
_run_async_entrypoint(
|
|
run_supervisor(plugins_dir=plugins_dir),
|
|
log_message=f"启动插件主管进程,插件目录:{plugins_dir}",
|
|
)
|
|
|
|
|
|
@cli.command(hidden=True)
|
|
@click.option(
|
|
"--plugin-dir",
|
|
required=False,
|
|
type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
|
|
)
|
|
@click.option(
|
|
"--group-metadata",
|
|
required=False,
|
|
type=click.Path(file_okay=True, dir_okay=False, path_type=Path),
|
|
)
|
|
def worker(plugin_dir: Path | None, group_metadata: Path | None) -> None:
|
|
"""Internal command used by the supervisor to start a worker."""
|
|
if plugin_dir is None and group_metadata is None:
|
|
raise click.UsageError("Either --plugin-dir or --group-metadata is required")
|
|
if plugin_dir is not None and group_metadata is not None:
|
|
raise click.UsageError(
|
|
"--plugin-dir and --group-metadata are mutually exclusive"
|
|
)
|
|
|
|
target = str(group_metadata or plugin_dir)
|
|
if group_metadata is not None:
|
|
entrypoint = run_plugin_worker(group_metadata=group_metadata)
|
|
else:
|
|
entrypoint = run_plugin_worker(plugin_dir=plugin_dir)
|
|
_run_async_entrypoint(
|
|
entrypoint,
|
|
log_message=f"启动插件工作进程:{target}",
|
|
log_level="debug",
|
|
)
|
|
|
|
|
|
@cli.command(hidden=True)
|
|
@click.option("--port", default=8765, type=int, help="WebSocket server port")
|
|
def websocket(port: int) -> None:
|
|
"""Legacy websocket runtime entrypoint."""
|
|
_run_async_entrypoint(
|
|
run_websocket_server(port=port),
|
|
log_message=f"启动 WebSocket 服务器,端口:{port}",
|
|
)
|