diff --git a/packages/builtin_commands/commands/admin.py b/packages/builtin_commands/commands/admin.py
index 2073f45a2..83d4b5974 100644
--- a/packages/builtin_commands/commands/admin.py
+++ b/packages/builtin_commands/commands/admin.py
@@ -71,6 +71,7 @@ class AdminCommands:
event.set_result(MessageEventResult().message("此 SID 不在白名单内。"))
async def update_dashboard(self, event: AstrMessageEvent):
+ """更新管理面板"""
await event.send(MessageChain().message("正在尝试更新管理面板..."))
await download_dashboard(version=f"v{VERSION}", latest=False)
await event.send(MessageChain().message("管理面板更新完成。"))
diff --git a/packages/builtin_commands/commands/help.py b/packages/builtin_commands/commands/help.py
index 7f5b6c170..092fc59ec 100644
--- a/packages/builtin_commands/commands/help.py
+++ b/packages/builtin_commands/commands/help.py
@@ -3,6 +3,7 @@ import aiohttp
from astrbot.api import star
from astrbot.api.event import AstrMessageEvent, MessageEventResult
from astrbot.core.config.default import VERSION
+from astrbot.core.star import command_management
from astrbot.core.utils.io import get_dashboard_version
@@ -21,6 +22,46 @@ class HelpCommand:
except BaseException:
return ""
+ async def _build_reserved_command_lines(self) -> list[str]:
+ """
+ 使用实时指令配置生成内置指令清单,确保重命名/禁用后与实际生效状态保持一致。
+ """
+ try:
+ commands = await command_management.list_commands()
+ except BaseException:
+ return []
+
+ lines: list[str] = []
+ hidden_commands = {"set", "unset", "websearch"}
+
+ def walk(items: list[dict], indent: int = 0):
+ for item in items:
+ if not item.get("reserved") or not item.get("enabled"):
+ continue
+ # 仅展示顶级指令或指令组
+ if item.get("type") == "sub_command":
+ continue
+ if item.get("parent_signature"):
+ continue
+
+ effective = (
+ item.get("effective_command")
+ or item.get("original_command")
+ or item.get("handler_name")
+ )
+ if not effective:
+ continue
+ if effective in hidden_commands:
+ continue
+
+ description = item.get("description") or ""
+ desc_text = f" - {description}" if description else ""
+ indent_prefix = " " * indent
+ lines.append(f"{indent_prefix}/{effective}{desc_text}")
+
+ walk(commands)
+ return lines
+
async def help(self, event: AstrMessageEvent):
"""查看帮助"""
notice = ""
@@ -30,34 +71,18 @@ class HelpCommand:
pass
dashboard_version = await get_dashboard_version()
+ command_lines = await self._build_reserved_command_lines()
+ commands_section = (
+ "\n".join(command_lines) if command_lines else "暂无启用的内置指令"
+ )
- msg = f"""AstrBot v{VERSION}(WebUI: {dashboard_version})
-内置指令:
-[System]
-/plugin: 查看插件、插件帮助
-/t2i: 开关文本转图片
-/tts: 开关文本转语音
-/sid: 获取会话 ID
-/op: 管理员
-/wl: 白名单
-/dashboard_update: 更新管理面板(op)
-/alter_cmd: 设置指令权限(op)
-
-[大模型]
-/llm: 开启/关闭 LLM
-/provider: 大模型提供商
-/model: 模型列表
-/ls: 对话列表
-/new: 创建新对话
-/groupnew 群号: 为群聊创建新对话(op)
-/switch 序号: 切换对话
-/rename 新名字: 重命名当前对话
-/del: 删除当前会话对话(op)
-/reset: 重置 LLM 会话
-/history: 当前对话的对话记录
-/persona: 人格情景(op)
-/key: API Key(op)
-/websearch: 网页搜索
-{notice}"""
+ msg_parts = [
+ f"AstrBot v{VERSION}(WebUI: {dashboard_version})",
+ "内置指令:",
+ commands_section,
+ ]
+ if notice:
+ msg_parts.append(notice)
+ msg = "\n".join(msg_parts)
event.set_result(MessageEventResult().message(msg).use_t2i(False))
diff --git a/packages/builtin_commands/main.py b/packages/builtin_commands/main.py
index 291bed456..7809c4359 100644
--- a/packages/builtin_commands/main.py
+++ b/packages/builtin_commands/main.py
@@ -49,7 +49,7 @@ class Main(star.Star):
@filter.command_group("tool")
def tool(self):
- pass
+ """函数工具管理"""
@tool.command("ls")
async def tool_ls(self, event: AstrMessageEvent):
@@ -73,7 +73,7 @@ class Main(star.Star):
@filter.command_group("plugin")
def plugin(self):
- pass
+ """插件管理"""
@plugin.command("ls")
async def plugin_ls(self, event: AstrMessageEvent):
@@ -219,6 +219,7 @@ class Main(star.Star):
@filter.permission_type(filter.PermissionType.ADMIN)
@filter.command("dashboard_update")
async def update_dashboard(self, event: AstrMessageEvent):
+ """更新管理面板"""
await self.admin_c.update_dashboard(event)
@filter.command("set")
diff --git a/packages/python_interpreter/main.py b/packages/python_interpreter/main.py
index 98496157a..afbef7560 100644
--- a/packages/python_interpreter/main.py
+++ b/packages/python_interpreter/main.py
@@ -249,7 +249,7 @@ class Main(star.Star):
@filter.command_group("pi")
def pi(self):
- pass
+ """代码执行器配置"""
@pi.command("absdir")
async def pi_absdir(self, event: AstrMessageEvent, path: str = ""):
diff --git a/packages/reminder/main.py b/packages/reminder/main.py
index 8f61e02fe..62af7ae56 100644
--- a/packages/reminder/main.py
+++ b/packages/reminder/main.py
@@ -179,7 +179,7 @@ class Main(star.Star):
@filter.command_group("reminder")
def reminder(self):
- """The command group of the reminder."""
+ """待办提醒"""
async def get_upcoming_reminders(self, unified_msg_origin: str):
"""Get upcoming reminders."""
diff --git a/packages/web_searcher/main.py b/packages/web_searcher/main.py
index 118ef2483..4745cd0c0 100644
--- a/packages/web_searcher/main.py
+++ b/packages/web_searcher/main.py
@@ -185,6 +185,7 @@ class Main(star.Star):
@filter.command("websearch")
async def websearch(self, event: AstrMessageEvent, oper: str | None = None):
+ """网页搜索指令(已废弃)"""
event.set_result(
MessageEventResult().message(
"此指令已经被废弃,请在 WebUI 中开启或关闭网页搜索功能。",
diff --git a/pyproject.toml b/pyproject.toml
index 6badf6d19..3c0a02e93 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
-version = "4.9.0"
+version = "4.9.2"
description = "Easy-to-use multi-platform LLM chatbot and development framework"
readme = "README.md"
requires-python = ">=3.10"
diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py
index f5439e9d5..969f0da6d 100644
--- a/tests/test_dashboard.py
+++ b/tests/test_dashboard.py
@@ -160,6 +160,34 @@ async def test_plugins(app: Quart, authenticated_header: dict):
assert exists is False, "插件 astrbot_plugin_essential 未成功卸载"
+@pytest.mark.asyncio
+async def test_commands_api(app: Quart, authenticated_header: dict):
+ """Tests the command management API endpoints."""
+ test_client = app.test_client()
+
+ # GET /api/commands - list commands
+ response = await test_client.get("/api/commands", headers=authenticated_header)
+ assert response.status_code == 200
+ data = await response.get_json()
+ assert data["status"] == "ok"
+ assert "items" in data["data"]
+ assert "summary" in data["data"]
+ summary = data["data"]["summary"]
+ assert "total" in summary
+ assert "disabled" in summary
+ assert "conflicts" in summary
+
+ # GET /api/commands/conflicts - list conflicts
+ response = await test_client.get(
+ "/api/commands/conflicts", headers=authenticated_header
+ )
+ assert response.status_code == 200
+ data = await response.get_json()
+ assert data["status"] == "ok"
+ # conflicts is a list
+ assert isinstance(data["data"], list)
+
+
@pytest.mark.asyncio
async def test_check_update(app: Quart, authenticated_header: dict):
test_client = app.test_client()