Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
063681a
feat(platform): add CLI platform adapter for testing
YukiRa1n Jan 31, 2026
ec4ca74
refactor(platform): rename CLI adapter to CLI Tester and disable by d…
YukiRa1n Jan 31, 2026
5f549ac
feat: add token-based authentication for CLI platform
YukiRa1n Jan 31, 2026
a20ef87
refactor(cli): fix hardcoded paths and improve code quality
YukiRa1n Jan 31, 2026
ce1c26f
feat(cli): support multi-round reply and large response handling
YukiRa1n Jan 31, 2026
c8eeb63
fix(ci): skip release step in forked repositories
YukiRa1n Jan 31, 2026
ecfb62e
fix(ci): correct checkout parameter from fetch-tag to fetch-tags
YukiRa1n Feb 1, 2026
b4b87b5
feat(cli): add cross-platform socket support for Windows compatibility
YukiRa1n Feb 2, 2026
1ef67c0
refactor(cli): 重构CLI适配器为模块化架构
YukiRa1n Feb 5, 2026
b8eb38a
feat: 添加 `astr` CLI 客户端命令
YukiRa1n Feb 5, 2026
ec03c31
chore: 删除独立的 脚本
YukiRa1n Feb 5, 2026
c32114b
docs: 改进 usage: astr [-h] [-s SOCKET] [-t TIMEOUT] [-j] [--log] [--li…
YukiRa1n Feb 5, 2026
a36feb0
docs: 修正 help 示例,移除命令前的 /
YukiRa1n Feb 5, 2026
2b34879
fix: 支持以 / 开头的命令参数
YukiRa1n Feb 5, 2026
7bc244b
fix: 修复 UTF-8 多字节字符解码问题
YukiRa1n Feb 5, 2026
ef179f9
feat: 改进 CLI 输出格式和添加日志查询功能
YukiRa1n Feb 5, 2026
94cba4b
chore: 改进日志查询错误提示
YukiRa1n Feb 5, 2026
0e52245
style: ruff format 和 lint 检查通过
YukiRa1n Feb 5, 2026
669c931
fix: 兼容 Git Bash 的路径转换问题
YukiRa1n Feb 5, 2026
e8a7638
fix: CLI 客户端支持全局调用
YukiRa1n Feb 5, 2026
e36f972
refactor(cli): 采纳PR review,argparse改为click + 修复asyncio弃用API
YukiRa1n Feb 6, 2026
2a9dbce
fix(cli): 完善旧用法兼容,astr -j/--log等flag自动路由到子命令
YukiRa1n Feb 6, 2026
a952d67
fix(cli): 修复日志级别筛选功能
YukiRa1n Feb 8, 2026
fe1095a
feat(cli): 默认使用文件模式读取日志
YukiRa1n Feb 8, 2026
be7bede
fix(cli): 修复插件指令执行后触发 LLM 回复的问题
YukiRa1n Feb 10, 2026
079d60c
feat(cli): 添加重启命令和可选的新窗口启动
YukiRa1n Feb 10, 2026
4ec2f60
feat(cli): Windows 默认在新窗口启动
YukiRa1n Feb 10, 2026
5833ff7
fix(cli): 优化消息接收逻辑,用 finalize 机制替代延迟响应
YukiRa1n Feb 12, 2026
50ccc88
fix(cli): 支持全局调用 run/restart,run 默认当前窗口
YukiRa1n Feb 12, 2026
0579643
fix(cli): 修复全局调用时路径定位错误
YukiRa1n Feb 12, 2026
f31f646
refactor(cli): modularize CLI client and add rich command set
YukiRa1n Feb 19, 2026
78d1a1f
refactor(cli): 扁平化CLI适配器结构,与其他适配器风格对齐
YukiRa1n Feb 19, 2026
3a7508e
ci: exclude test_cli from default pytest run
YukiRa1n Feb 19, 2026
632a54a
feat(cli): add function tool listing and calling via astr tool
YukiRa1n Feb 19, 2026
441779c
perf: lazy init logger in astrbot/__init__.py to avoid core loading o…
YukiRa1n Feb 19, 2026
a8f5e86
style: ruff format
YukiRa1n Feb 24, 2026
08bac1e
feat(cli): add cross-session browsing via astr session ls/convs/history
YukiRa1n Feb 25, 2026
59d4198
refactor(cli): remove interactive/TTY mode
YukiRa1n Feb 25, 2026
331226d
feat(cli): add astrbot stop command to terminate running processes
YukiRa1n Feb 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dashboard_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
!dist/**/*.md

- name: Create GitHub Release
if: github.event_name == 'push'
if: github.event_name == 'push' && github.repository == 'AstrBotDevs/AstrBot'
uses: ncipollo/release-action@v1
with:
tag: release-${{ github.sha }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 1
fetch-tag: true
fetch-tags: true

- name: Check for new commits today
if: github.event_name == 'schedule'
Expand Down Expand Up @@ -121,7 +121,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 1
fetch-tag: true
fetch-tags: true

- name: Get latest tag (only on manual trigger)
id: get-latest-tag
Expand Down
10 changes: 8 additions & 2 deletions astrbot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from .core.log import LogManager
def __getattr__(name: str):
"""延迟初始化 logger,避免 CLI 客户端导入时触发 core 全量初始化"""
if name == "logger":
from .core.log import LogManager

logger = LogManager.GetLogger(log_name="astrbot")
_logger = LogManager.GetLogger(log_name="astrbot")
globals()["logger"] = _logger
return _logger
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
4 changes: 3 additions & 1 deletion astrbot/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import click

from . import __version__
from .commands import conf, init, plug, run
from .commands import conf, init, plug, restart, run, stop

logo_tmpl = r"""
___ _______.___________..______ .______ ______ .___________.
Expand Down Expand Up @@ -51,6 +51,8 @@ def help(command_name: str | None) -> None:

cli.add_command(init)
cli.add_command(run)
cli.add_command(restart)
cli.add_command(stop)
cli.add_command(help)
cli.add_command(plug)
cli.add_command(conf)
Expand Down
4 changes: 4 additions & 0 deletions astrbot/cli/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""AstrBot CLI Client - Socket客户端工具

用于通过Unix Socket或TCP Socket与AstrBot CLIPlatformAdapter通信
"""
152 changes: 152 additions & 0 deletions astrbot/cli/client/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""
AstrBot CLI Client - 跨平台Socket客户端

支持Unix Socket和TCP Socket连接到CLIPlatformAdapter

用法:
astr "你好"
astr "/help"
echo "你好" | astr
"""

import io
import sys

import click

# Windows UTF-8 输出支持(仅在非测试环境下替换,避免与 pytest capture 冲突)
if sys.platform == "win32" and "pytest" not in sys.modules:
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")


EPILOG = """
命令总览 (所有命令均支持 -j/--json 输出原始 JSON):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[发送消息]
astr <message> 直接发送消息(隐式调用 send)
astr send <message> 显式发送消息给 AstrBot
astr send -t 60 <message> 设置超时(秒),默认 30
echo "msg" | astr 从管道读取消息

[会话管理] astr conv <子命令>
astr conv ls [page] 列出所有对话(可翻页)
astr conv new 创建新对话并切换到该对话
astr conv switch <index> 按序号切换对话(序号见 conv ls)
astr conv del 删除当前对话
astr conv rename <name> 重命名当前对话
astr conv reset 清除当前对话的 LLM 上下文
astr conv history [page] 查看当前对话的聊天记录

[插件管理] astr plugin <子命令>
astr plugin ls 列出已安装插件及状态
astr plugin on <name> 启用指定插件
astr plugin off <name> 禁用指定插件
astr plugin help [name] 查看插件帮助(省略 name 则查看全部)

[LLM 配置]
astr provider [index] 查看 Provider 列表 / 按序号切换
astr model [index|name] 查看模型列表 / 按序号或名称切换
astr key [index] 查看 API Key 列表 / 按序号切换

[快捷命令]
astr help 查看 AstrBot 服务端内置指令帮助
astr sid 查看当前会话 ID 和管理员 ID
astr t2i 开关文字转图片(会话级别)
astr tts 开关文字转语音(会话级别)

[日志查看]
astr log 读取最近 100 行日志(直接读文件)
astr log --lines 50 指定行数
astr log --level ERROR 按级别过滤 (DEBUG/INFO/WARNING/ERROR)
astr log --pattern "关键词" 按关键词过滤(--regex 启用正则)
astr log --socket 通过 Socket 从服务端获取日志

[调试工具]
astr ping [-c N] 测试连通性和延迟(-c 指定次数)
astr status 查看连接配置、Token、服务状态
astr test echo <message> 发送消息并查看完整回环响应
astr test plugin <cmd> <args> 测试插件命令(发送 /<cmd> <args>)
例: astr test plugin probe cpu
→ 实际发送 /probe cpu

[函数工具] astr tool <子命令>
astr tool ls 列出所有注册的函数工具
astr tool ls -o plugin 按来源过滤(plugin/mcp/builtin)
astr tool info <name> 查看工具详细信息和参数
astr tool call <name> [json] 调用工具,例: astr tool call my_func '{"k":"v"}'

[跨会话浏览] astr session <子命令>
astr session ls 列出所有会话(跨平台:QQ/TG/微信/CLI…)
astr session ls -P qq 按平台过滤(-q 搜索关键词)
astr session convs <session_id> 查看该会话下的对话列表
astr session history <sid> 查看聊天记录(-c 指定对话,默认当前)

[批量执行]
astr batch <file> 从文件逐行读取并执行命令
(# 开头为注释,空行跳过)

兼容旧用法: astr --log = astr log | astr -j "msg" = astr send -j "msg"

连接: 自动读取 data/.cli_connection 和 data/.cli_token
需在 AstrBot 根目录运行,或设置 ASTRBOT_ROOT 环境变量
"""


class RawEpilogGroup(click.Group):
"""保留 epilog 原始格式的 Group,同时支持默认子命令路由"""

def format_epilog(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
if self.epilog:
formatter.write("\n")
for line in self.epilog.split("\n"):
formatter.write(line + "\n")

# send 子命令的 option 前缀,用于识别 astr -j "你好" 等旧用法
_send_opts = {"-j", "--json", "-t", "--timeout", "-s", "--socket"}
# --log 旧用法映射到 log 子命令
_log_flag = {"--log"}

def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
if args:
first = args[0]
if first in self._log_flag:
# astr --log ... → astr log ...
args = ["log"] + args[1:]
elif first not in self.commands:
if not first.startswith("-") or first in self._send_opts:
# astr 你好 / astr -j "你好" → astr send ...
args = ["send"] + args
return super().parse_args(ctx, args)


@click.group(
cls=RawEpilogGroup,
invoke_without_command=True,
epilog=EPILOG,
)
@click.pass_context
def main(ctx: click.Context) -> None:
"""AstrBot CLI Client - 与 AstrBot 交互的命令行工具"""
if ctx.invoked_subcommand is None:
# 无子命令时,检查 stdin 是否有管道输入
if not sys.stdin.isatty():
message = sys.stdin.read().strip()
if message:
from .commands.send import do_send

do_send(message, None, 30.0, False)
return
click.echo(ctx.get_help())


# 注册所有子命令
from .commands import register_commands # noqa: E402

register_commands(main)


if __name__ == "__main__":
main()
97 changes: 97 additions & 0 deletions astrbot/cli/client/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""命令注册模块 - 将所有子命令注册到主 CLI group"""

import click

from .conv import conv
from .debug import ping, status, test
from .log import log
from .plugin import plugin
from .provider import key, model, provider
from .send import send
from .session import session
from .tool import tool


def register_commands(group):
"""将所有子命令注册到 CLI group

Args:
group: click.Group 实例
"""
# 核心命令
group.add_command(send)
group.add_command(log)

# 会话管理
group.add_command(conv)

# 跨会话浏览
group.add_command(session)

# 插件管理
group.add_command(plugin)

# Provider/Model/Key
group.add_command(provider)
group.add_command(model)
group.add_command(key)

# 调试工具
group.add_command(ping)
group.add_command(status)
group.add_command(test)

# 函数工具管理
group.add_command(tool)

# 快捷别名(独立命令,映射到 send /cmd)
_register_aliases(group)


def _register_aliases(group):
"""注册快捷别名命令"""
from .. import connection, output

@group.command(name="help", help="查看 AstrBot 内置命令帮助")
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def help_cmd(use_json):
response = connection.send_message("/help")
output.output_response(response, use_json)

@group.command(name="sid", help="查看当前会话 ID 和管理员 ID")
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def sid_cmd(use_json):
response = connection.send_message("/sid")
output.output_response(response, use_json)

@group.command(name="t2i", help="开关文字转图片(会话级别)")
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def t2i_cmd(use_json):
response = connection.send_message("/t2i")
output.output_response(response, use_json)

@group.command(name="tts", help="开关文字转语音(会话级别)")
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def tts_cmd(use_json):
response = connection.send_message("/tts")
output.output_response(response, use_json)

@group.command(name="batch", help="从文件批量执行命令")
@click.argument("file", type=click.Path(exists=True))
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def batch_cmd(file, use_json):
"""从文件逐行读取并执行命令

\b
示例:
astr batch commands.txt 批量执行文件中的命令
"""
with open(file, encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line or line.startswith("#"):
continue
click.echo(f"[{line_num}] > {line}")
response = connection.send_message(line)
output.output_response(response, use_json)
click.echo("")
73 changes: 73 additions & 0 deletions astrbot/cli/client/commands/conv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""会话管理命令组 - astr conv"""

import click

from ..connection import send_message
from ..output import output_response


@click.group(help="会话管理 (子命令: ls/new/switch/del/rename/reset/history)")
def conv() -> None:
"""会话管理命令组"""


@conv.command(name="ls", help="列出当前会话的所有对话")
@click.argument("page", default="", required=False)
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def conv_ls(page: str, use_json: bool) -> None:
"""列出对话"""
cmd = "/ls" if not page else f"/ls {page}"
response = send_message(cmd)
output_response(response, use_json)


@conv.command(name="new", help="创建新对话")
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def conv_new(use_json: bool) -> None:
"""创建新对话"""
response = send_message("/new")
output_response(response, use_json)


@conv.command(name="switch", help="按序号切换对话")
@click.argument("index", type=int)
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def conv_switch(index: int, use_json: bool) -> None:
"""按序号切换对话"""
response = send_message(f"/switch {index}")
output_response(response, use_json)


@conv.command(name="del", help="删除当前对话")
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def conv_del(use_json: bool) -> None:
"""删除当前对话"""
response = send_message("/del")
output_response(response, use_json)


@conv.command(name="rename", help="重命名当前对话")
@click.argument("name")
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def conv_rename(name: str, use_json: bool) -> None:
"""重命名当前对话"""
response = send_message(f"/rename {name}")
output_response(response, use_json)


@conv.command(name="reset", help="重置当前 LLM 会话")
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def conv_reset(use_json: bool) -> None:
"""重置当前 LLM 会话"""
response = send_message("/reset")
output_response(response, use_json)


@conv.command(name="history", help="查看对话记录")
@click.argument("page", default="", required=False)
@click.option("-j", "--json", "use_json", is_flag=True, help="输出原始 JSON")
def conv_history(page: str, use_json: bool) -> None:
"""查看对话记录"""
cmd = "/history" if not page else f"/history {page}"
response = send_message(cmd)
output_response(response, use_json)
Loading