10. CLI 与 UI 系统:构建交互式 Agent 界面
10.1. 为什么 Agent 需要精心设计的 UI
在传统的认知中,命令行工具只是一种朴素的人机接口——黑底白字,输入命令,得到输出。 然而,当我们将大语言模型(LLM)引入终端后,CLI 的角色发生了质变: 它不再只是"执行命令的工具",而是一个 人机协作的实时交互界面 。
Hermes Agent 的 CLI 面临的设计挑战远超一般终端应用:
长时间运行的异步任务 :Agent 的一次请求可能触发多次工具调用(终端命令、文件读写、网页搜索), 总耗时从数秒到数分钟不等。用户需要一个实时反馈机制来了解 Agent 正在做什么。
多模态交互 :用户可能需要输入密码(sudo)、选择方案(clarify)、审批危险命令(approval), 这些交互模式需要安全的输入通道,且不能干扰正在运行的任务。
丰富的视觉反馈 :工具执行状态、文件编辑 diff、token 使用量、模型推理过程—— 这些信息的展示方式直接影响用户的工作效率。
主题个性化 :不同用户有不同的审美偏好,有人喜欢暗色主题,有人需要亮色适配, 而有些用户希望 Agent 拥有独特的人格化视觉风格。
Hermes 的答案是:用 prompt_toolkit 构建一个全功能的 TUI(Terminal User Interface), 同时为非交互式场景(管道、Docker、systemd)提供优雅降级。 皮肤系统让用户可以一键切换视觉风格,Kawaii Spinner 为等待过程注入趣味, 内联 diff 让代码审查变得直观——这些细节共同构成了一套 令人愉悦的 Agent 交互体验 。
本章将从架构总览开始,深入剖析 Hermes CLI 的每一个 UI 组件。
10.2. CLI 架构总览
Hermes CLI 的启动流程遵循一个清晰的分层架构。从用户在终端输入 hermes 开始,
到 REPL 循环就绪,经历了以下阶段:
flowchart TD
A["用户执行 hermes"] --> B["hermes_cli/main.py<br/>argparse 子命令解析"]
B --> C{"子命令类型?"}
C -->|chat / 默认| D["HermesCLI() 初始化"]
C -->|gateway| E["tui_gateway/entry.py<br/>JSON-RPC 网关"]
C -->|setup| F["交互式配置向导"]
C -->|其他子命令| G["一次性执行并退出"]
D --> H["load_cli_config()<br/>加载 config.yaml"]
H --> I["init_skin_from_config()<br/>初始化皮肤引擎"]
I --> J["HermesCLI.run()<br/>启动 REPL 循环"]
J --> K["prompt_toolkit Application"]
K --> L["Layout 构建<br/>输入区 + Spinner + 状态栏"]
K --> M["KeyBindings 注册<br/>Enter / Ctrl+C / Tab"]
K --> N["Completer 注册<br/>Slash 命令补全"]
K --> O["AutoSuggest 注册<br/>历史建议"]
L --> P["REPL 主循环<br/>等待输入 → 路由 → 执行"]
P --> Q{"输入类型?"}
Q -->|Slash 命令| R["命令处理器"]
Q -->|普通文本| S["AIAgent.run_conversation()"]
Q -->|文件拖放| T["附件处理"]
S --> U["流式输出 / Spinner / Diff"]
U --> P
CLI 架构总览
10.2.1. 入口点解析
hermes_cli/main.py 是整个系统的入口。它使用 Python Fire 库将 HermesCLI 类
暴露为 CLI 命令,但在此之前完成了一系列关键的初始化工作:
Profile 覆盖机制 :在所有模块导入之前,从 sys.argv 中解析 --profile / -p 参数,
设置 HERMES_HOME 环境变量。这是因为许多模块在导入时就会缓存 HERMES_HOME 的值(模块级常量),
如果不在最早时机设置,后续的配置路径就会错误。
.env 加载 :按照优先级顺序加载环境变量——先 ~/.hermes/.env ,再项目根目录的 .env 。
用户管理的 .env 文件应该覆盖过时的 shell 导出值。
配置桥接 :load_cli_config() 从 config.yaml 读取配置,将终端配置映射为环境变量(如
terminal.env_type → TERMINAL_ENV),这样底层的 terminal_tool 可以通过
os.getenv() 获取配置值。
配置加载的优先级链条为:CLI 参数 > 环境变量 > config.yaml > 默认值。
10.2.2. HermesCLI 类的生命周期
HermesCLI 是整个交互式 REPL 的核心。它的初始化参数包括:
model:使用的模型名称(如anthropic/claude-sonnet-4)toolsets:启用的工具集列表(如["web", "terminal", "file"])provider:推理服务提供者(如auto、openrouter、openai)compact:紧凑显示模式resume:恢复之前的会话 IDcheckpoints:启用文件系统检查点
初始化时会设置大量状态变量,包括对话历史、输入队列、中断队列、各种交互状态
(clarify、sudo、approval、secret、model picker)、语音模式状态等。
AIAgent 实例本身被延迟创建——直到用户发送第一条消息时才真正初始化,避免启动时的长时间等待。
10.3. prompt_toolkit 集成
Hermes 使用 prompt_toolkit 库来构建一个功能完备的 TUI。
这是一个被 IPython、ptpython 等项目广泛使用的高质量终端 UI 框架。
10.3.1. Layout 布局结构
TUI 的布局由 HSplit 垂直排列的多个区域组成:
# 简化的布局结构
layout = HSplit([
Window(FormattedTextControl(input_area), height=Dimension(min=1)), # 输入区
Window(FormattedTextControl(spinner_widget), height=spinner_height), # Spinner
ConditionalContainer( # 状态栏
Window(FormattedTextControl(status_bar), height=1),
filter=Condition(lambda: self._status_bar_visible),
),
])
输入区(Input Area) :用户输入提示符和文本的地方。它显示 ❯ `` 提示符号(可通过皮肤系统自定义),
并支持多行输入。当有图片附件时,会在输入区上方显示附件徽章(如 ``[📎 Image #1])。
Spinner 区域 :Agent 运行时显示的动态等待指示器。它会显示当前工具名称、执行时间、 以及随机出现的可爱表情。当 Agent 空闲时,此区域高度为 0,自动隐藏。
状态栏(Status Bar) :显示当前模型名称、上下文使用百分比、会话时长等关键信息。
状态栏的可见性可以通过 /statusbar 命令切换。
此外,布局还包含一个 CompletionsMenu——当用户按下 Tab 键时,会弹出一个浮动的补全菜单,
显示匹配的 slash 命令。
10.3.2. KeyBindings 键绑定
Hermes 注册了丰富的键绑定来支持各种交互模式:
Enter 键 :根据当前状态有三种行为——
正常模式:提交用户输入
Agent 运行中(interrupt 模式):中断当前 Agent 执行
交互模式(clarify/approval/sudo/secret):确认当前选择
Ctrl+C 键 :五级优先级的中断处理(详见后文)。
Tab 键 :触发 slash 命令补全。
方向键(上/下) :在 clarify 模式中导航选项,在正常模式中浏览历史。
Ctrl+B :切换语音录制模式。
10.3.3. Completer 补全器
SlashCommandCompleter 是一个 prompt_toolkit.Completer 子类,
它从 COMMAND_REGISTRY 中获取所有已注册的命令,并提供前缀匹配补全。
补全逻辑:
当用户输入以
/开头时,激活命令补全模式收集所有命令名称和别名,进行前缀匹配
匹配结果按字母排序,显示命令描述作为 meta 信息
对于有子命令的命令(如
/reasoning),在空格后提供子命令补全集成 skill 命令——通过
skill_commands_provider回调动态获取可用的 skill 列表
10.3.4. AutoSuggest 自动建议
SlashCommandAutoSuggest 基于 FileHistory 提供历史输入建议。
当用户开始输入时,它会查找最匹配的历史记录并显示为灰色提示文本。
用户只需按右箭头键即可接受建议。
10.4. 输入路由优先级
Hermes CLI 的输入处理遵循一个 九级优先级 系统。当用户按下 Enter 键时, 输入会被依次检查,直到匹配到某个处理器:
flowchart TD
INPUT["用户按 Enter"] --> P1{"Level 1: sudo_state?"}
P1 -->|是| SUDO["提交 sudo 密码"]
P1 -->|否| P2{"Level 2: secret_state?"}
P2 -->|是| SECRET["提交 secret 值"]
P2 -->|否| P3{"Level 3: approval_state?"}
P3 -->|是| APPROVAL["确认审批选择"]
P3 -->|否| P4{"Level 4: model_picker?"}
P4 -->|是| MODEL["确认模型选择"]
P4 -->|否| P5{"Level 5: clarify_state?"}
P5 -->|是| CLARIFY["确认 clarify 选择<br/>或提交自由文本"]
P5 -->|否| P6{"Level 6: 空输入?"}
P6 -->|是| IGNORE["忽略空输入"]
P6 -->|否| P7{"Level 7: Agent 运行中?"}
P7 -->|是 + interrupt 模式| INTERRUPT["中断 Agent"]
P7 -->|是 + queue 模式| QUEUE["排队等待"]
P7 -->|否| P8{"Level 8: _pending_input?"}
P8 -->|有| PENDING["从队列取出输入"]
P8 -->|无| P9{"Level 9: 正常输入"}
P9 --> SLASH{"是 Slash 命令?"}
SLASH -->|是| CMD["执行命令"]
SLASH -->|否| AGENT["发送给 Agent"]
输入路由优先级
这个设计确保了无论 Agent 处于什么状态,用户的紧急交互(密码输入、命令审批) 总是能被正确路由,不会被遗漏或阻塞。
10.4.1. 各级别详细说明
Level 1 — Sudo :当 terminal_tool 需要提升权限时,CLI 进入 sudo 输入模式。
输入会被直接路由到 sudo 密码回调,不会经过任何其他处理。密码以隐藏形式显示。
Level 2 — Secret :类似 sudo,用于安全地输入 API Key 等敏感信息。
通过 save_env_value_secure() 存储到 ~/.hermes/.env ,从不暴露给模型。
Level 3 — Approval :当 Agent 尝试执行危险命令时,CLI 显示审批 UI。
用户可以选择 once (本次允许)、session (本次会话允许)、always (永久允许)、deny (拒绝)。
Level 4 — Model Picker :/model 命令触发的模型选择 UI。用户可以通过方向键导航可用模型列表。
Level 5 — Clarify :当 Agent 需要用户澄清时,显示选择题界面。 支持方向键导航和 "Other" 选项(自由文本输入)。超时后 Agent 自行决定。
Level 6 — 空输入 :纯空白输入被直接忽略,不触发任何操作。
Level 7 — Agent 运行中 :取决于 busy_input_mode 配置——
interrupt(默认):Enter 键中断当前 Agent 执行queue:输入被排队,等待 Agent 完成后自动发送
Level 8 — 待处理输入队列 :某些命令(如 /queue 、/steer)会将消息放入 _pending_input 队列。
Level 9 — 正常输入 :检查是否以 / 开头(slash 命令),否则作为普通消息发送给 Agent。
10.5. Slash 命令系统
Hermes 的 slash 命令系统采用 声明式注册 架构,通过一个中心化的注册表管理所有命令。
10.5.1. CommandDef 数据类
每个命令由 CommandDef 数据类定义:
@dataclass(frozen=True)
class CommandDef:
name: str # 命令名(不含斜杠)
description: str # 人类可读描述
category: str # 分类:Session, Configuration 等
aliases: tuple[str, ...] = () # 别名
args_hint: str = "" # 参数提示
subcommands: tuple[str, ...] = () # Tab 补全子命令
cli_only: bool = False # 仅 CLI 可用
gateway_only: bool = False # 仅网关可用
gateway_config_gate: str | None = None # 配置门控
命令注册表(COMMAND_REGISTRY)包含约 50 个命令,按功能分为六大类:
分类 |
代表命令 |
数量 |
|---|---|---|
Session |
/new, /clear, /history, /save, /retry, /undo, /title, /branch, /compress, /rollback, /stop |
~18 |
Configuration |
/model, /provider, /personality, /skin, /yolo, /reasoning, /fast, /voice |
~10 |
Tools & Skills |
/tools, /skills, /cron, /reload, /reload-mcp, /browser, /plugins |
~8 |
Info |
/help, /usage, /insights, /copy, /paste, /image, /debug |
~8 |
Exit |
/quit, /exit |
1 |
10.5.2. 命令解析流程
sequenceDiagram
autonumber
participant User as 用户
participant PT as prompt_toolkit
participant CR as COMMAND_REGISTRY
participant Handler as 命令处理器
participant Agent as AIAgent
User->>PT: 输入 /bg some task
PT->>PT: Tab 补全(如果按下)
Note over PT: SlashCommandCompleter<br/>匹配 /background
User->>PT: 按 Enter
PT->>CR: resolve_command("bg")
alt 命令名匹配
CR-->>PT: CommandDef(name="background", aliases=("bg",))
else 命令名不匹配
CR-->>PT: None → 作为普通文本发送
end
PT->>Handler: 执行 background 命令
Handler->>Agent: 创建后台任务
Handler-->>PT: 返回结果
Slash 命令解析流程
10.5.3. 别名解析机制
resolve_command() 函数处理命令名解析:
去除输入中的前导斜杠(
/)转换为小写
在
_COMMAND_LOOKUP字典中查找
这个查找表在模块导入时由 _build_command_lookup() 构建,
它为每个命令的主名和所有别名创建映射。例如:
_COMMAND_LOOKUP = {
"background": CommandDef(name="background", ...),
"bg": CommandDef(name="background", ...), # alias
"exit": CommandDef(name="quit", ...), # alias
"q": CommandDef(name="queue", ...), # alias
"snap": CommandDef(name="snapshot", ...), # alias
}
这样,用户输入 /bg some task 和 /background some task 会被路由到同一个处理器。
10.5.4. Tab 补全实现
SlashCommandCompleter 实现了 prompt_toolkit.Completer 接口:
解析当前输入,提取出命令名部分和参数部分
如果只有命令名(无空格),提供命令名补全
如果已有空格,检查是否有
subcommands定义,提供子命令补全额外集成 skill 命令——通过
skill_commands_provider动态获取可用的 skill 列表
补全菜单的样式由皮肤系统控制——completion_menu_bg 、completion_menu_current_bg 等颜色键。
10.6. 皮肤/主题引擎
皮肤引擎是 Hermes CLI 最独特的视觉特性之一。它允许用户通过一个 YAML 文件 完全自定义 CLI 的外观,无需修改任何代码。
10.6.1. SkinConfig 数据结构
SkinConfig 是皮肤配置的核心数据类:
@dataclass
class SkinConfig:
name: str
description: str = ""
colors: Dict[str, str] = field(default_factory=dict) # 20+ 颜色键
spinner: Dict[str, Any] = field(default_factory=dict) # Spinner 自定义
branding: Dict[str, str] = field(default_factory=dict) # 品牌文案
tool_prefix: str = "┊" # 工具输出前缀
tool_emojis: Dict[str, str] = field(default_factory=dict) # 工具表情覆盖
banner_logo: str = "" # Rich 标记 ASCII 艺术
banner_hero: str = "" # Rich 标记英雄图案
10.6.2. 内置主题
Hermes 提供了 8+ 个内置主题,每个都有独特的视觉风格:
名称 |
描述 |
色调 |
特殊元素 |
|---|---|---|---|
default |
经典 Hermes 金色/可爱 |
金色 #FFD700 / 青铜色 |
Kawaii 表情、蛇杖图案 |
ares |
战神主题 — 深红与青铜 |
深红 #9F1C1C / 青铜 #C7A96B |
⚔ Spinner翅膀、战神ASCII |
mono |
单色灰度 |
灰度 #555555 ~ #e6edf3 |
简洁专业 |
slate |
冷蓝开发者 |
蓝色 #4169e1 / #7eb8f6 |
技术风格 |
daylight |
亮色主题(浅色终端) |
蓝色 #2563EB / 深色文字 |
完整亮色UI |
warm-lightmode |
暖棕/金色(浅色终端) |
棕色 #5C3D11 / 金色 |
温暖舒适 |
poseidon |
海神主题 — 深蓝与海沫 |
蓝色 #2A6FB9 / 海沫 #A9DFFF |
Ψ 三叉戟图案 |
sisyphus |
西西弗斯主题 — 严谨灰度 |
灰度 #4A4A4A ~ #F5F5F5 |
巨石 ASCII、坚持语录 |
charizard |
喷火龙主题 — 火山橙 |
橙色 #C75B1D / 琥珀 #FFD39A |
✦ 火焰图案 |
10.6.3. 颜色键系统
皮肤系统定义了 20+ 个颜色键,每个键控制 UI 中的一个特定元素:
颜色键 |
控制元素 |
默认值 |
|---|---|---|
banner_border |
横幅边框 |
#CD7F32(青铜色) |
banner_title |
横幅标题文字 |
#FFD700(金色) |
ui_accent |
通用 UI 强调色 |
#FFBF00(琥珀色) |
ui_ok / ui_error / ui_warn |
成功/错误/警告指示器 |
绿/红/橙 |
prompt |
输入提示文字颜色 |
#FFF8DC(乳白色) |
input_rule |
输入区分隔线 |
#CD7F32 |
response_border |
响应框边框 |
#FFD700 |
status_bar_bg |
状态栏背景色 |
#1a1a2e(深蓝黑) |
completion_menu_* |
补全菜单(4个键) |
深色背景 |
10.6.4. 继承机制
classDiagram
class SkinConfig {
+name: str
+description: str
+colors: Dict
+spinner: Dict
+branding: Dict
+tool_prefix: str
+tool_emojis: Dict
+banner_logo: str
+banner_hero: str
+get_color(key, fallback) str
+get_spinner_wings() List
+get_branding(key, fallback) str
}
class DefaultSkin {
金色/青铜色配色
Kawaii spinner 表情
Hermes 品牌
}
class UserSkin {
仅覆盖部分颜色键
其余从 default 继承
}
class AresSkin {
深红/青铜配色
自定义 spinner 翅膀
战神品牌
}
DefaultSkin <|-- AresSkin : 完整定义
DefaultSkin <|-- UserSkin : 部分覆盖
note for UserSkin "_build_skin_config() 将<br/>default 的值作为基础,<br/>然后用用户的值覆盖"
note for SkinConfig "所有字段都是可选的。<br/>缺失值自动继承 default 皮肤。"
皮肤引擎继承机制
_build_skin_config() 函数实现了继承逻辑:
以
default皮肤的值为基础用用户提供的值覆盖对应字段
返回一个完整的
SkinConfig实例
这意味着用户只需要定义想要修改的颜色,其余自动继承。例如,
一个只修改了 banner_border 和 prompt_symbol 的用户皮肤:
name: mytheme
description: 只改了边框和提示符
colors:
banner_border: "#FF00FF"
branding:
prompt_symbol: ">>> "
其余 20+ 个颜色键会自动使用 default 皮肤的值。
10.6.5. prompt_toolkit 样式桥接
get_prompt_toolkit_style_overrides() 函数将皮肤颜色键映射为
prompt_toolkit 的样式类名。例如:
{
"input-area": prompt, # 输入区文字颜色
"status-bar": f"bg:{status_bg} {text}", # 状态栏背景+前景
"clarify-selected": f"{title} bold", # Clarify 选中项
"approval-title": f"{warn} bold", # Approval 标题
"sudo-prompt": f"{error} bold", # Sudo 提示
"completion-menu": f"bg:{menu_bg} {text}",# 补全菜单
}
这套桥接机制确保 /skin 命令切换后,TUI 的所有元素(包括补全菜单、交互 UI)
都会立即反映新的配色方案。
10.7. KawaiiSpinner
KawaiiSpinner 是 Hermes CLI 的标志性 UI 组件——一个带有可爱表情的动画等待指示器。
10.7.1. 9 种动画风格
Spinner 支持以下动画类型,每种都有独特的视觉节奏:
名称 |
帧序列 |
视觉效果 |
|---|---|---|
dots |
⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ |
经典 braille 旋转 |
bounce |
⠁⠂⠄⡀⢀⠠⠐⠈ |
弹跳线条 |
grow |
▁▂▃▄▅▆▇█▇▆▅▄▃▂ |
生长柱状图 |
arrows |
←↖↑↗→↘↓↙ |
箭头旋转 |
star |
✶✷✸✹✺✹✸✷ |
星形闪烁 |
moon |
🌑🌒🌓🌔🌕🌖🌗🌘 |
月相变化 |
pulse |
◜◠◝◞◡◟ |
脉冲圆弧 |
brain |
🧠💭💡✨💫🌟💡💭 |
大脑思维 |
sparkle |
⁺˚*✧✦✧*˚ |
闪烁星光 |
10.7.2. Kawaii 表情
除了动画帧,Spinner 还定义了两套表情:
等待表情(KAWAII_WAITING) :
"(。◕‿◕。)", "(◕‿◕✿)", "٩(◕‿◕。)۶", "(✿◠‿◠)", "( ˘▽˘)っ",
"♪(´ε` )", "(◕ᴗ◕✿)", "ヾ(^∇^)", "(≧◡≦)", "(★ω★)"
思考表情(KAWAII_THINKING) :
"(。•́︿•̀。)", "(◔_◔)", "(¬‿¬)", "( •_•)>⌐■-■", "(⌐■_■)",
"(´・_・`)", "◉_◉", "(°ロ°)", "( ˘⌣˘)♡", "ヽ(>∀<☆)☆"
思考动词(THINKING_VERBS) :
"pondering", "contemplating", "musing", "cogitating", "ruminating",
"deliberating", "mulling", "reflecting", "processing", "reasoning",
"analyzing", "computing", "synthesizing", "formulating", "brainstorming"
这些表情和动词都支持通过皮肤系统自定义。例如,ares 主题使用战神风格的动词:
"forging", "marching", "sizing the field", "holding the line",
"hammering plans", "tempering steel", "plotting impact", "raising the shield"
10.7.3. 线程安全渲染
KawaiiSpinner 在一个独立的守护线程中运行动画循环:
def start(self):
self.running = True
self.start_time = time.time()
self.thread = threading.Thread(target=self._animate, daemon=True)
self.thread.start()
动画循环每 0.12 秒刷新一帧,使用 \r 回车符覆盖当前行(而不是打印新行),
这样 spinner 始终保持在同一行。通过 self.last_line_len 记录上一行的长度,
在写入新帧时用空格填充差值,确保旧内容被完全覆盖。
print_above() 方法允许在 spinner 上方打印文本而不破坏动画:
先清除 spinner 行,打印文本,然后让下一帧重新绘制 spinner。
10.7.4. 环境适配
Spinner 会根据运行环境自动调整行为:
TTY 检测 :通过 self._out.isatty() 判断标准输出是否连接到真正的终端。
如果输出被重定向到文件或管道(Docker、systemd),跳过所有动画,
只打印一行静态的 [tool] message 和 [done] message 。
prompt_toolkit StdoutProxy 检测 :当运行在 patch_stdout() 上下文中时,
sys.stdout 被 prompt_toolkit 的 StdoutProxy 包装,它会在每次刷新时注入换行符,
导致 \r 覆盖失败——每个 spinner 帧都出现在新行上。在这种情况下,spinner 退化为
一个空循环(time.sleep(0.1)),让 TUI 的 _spinner_text 小部件接管显示。
10.8. 工具预览系统
当 Agent 调用工具时,CLI 需要在紧凑的一行中展示工具调用的关键信息。
这就是 build_tool_preview() 和 get_cute_tool_message() 的工作。
10.8.1. build_tool_preview()
这个函数接受工具名和参数字典,返回一个简短的预览字符串:
工具名 |
参数 |
预览输出 |
|---|---|---|
terminal |
{"command": "npm test"} |
npm test |
web_search |
{"query": "python async"} |
python async |
read_file |
{"path": "/src/main.py"} |
/src/main.py |
write_file |
{"path": "/src/main.py"} |
/src/main.py |
search_files |
{"pattern": "TODO", "target": "content"} |
TODO |
memory |
{"action": "add", "target": "user", "content": "likes cats"} |
+user: "likes cats" |
todo |
{"todos": [...], "merge": false} |
planning 3 task(s) |
对于没有专门处理逻辑的工具,函数会尝试从一组候选参数键(query, text,
command, path, name, prompt, code, goal)中提取预览文本。
10.8.2. get_cute_tool_message()
这个函数生成工具完成后的格式化输出行:
┊ 🔍 search python async 2.3s
┊ 💻 $ npm test 1.5s
┊ 📖 read /src/main.py 0.1s
┊ ✍️ write /src/main.py 0.3s
┊ 🔧 patch /src/main.py 0.2s
格式为 | {emoji} {verb:9} {detail} {duration} ,其中:
前缀字符 (
┊):由皮肤的tool_prefix控制emoji :由皮肤的
tool_emojis覆盖或工具注册表的默认值动词 :左对齐 9 字符(如
search,$,read,write)详情 :截断到 35-42 字符
持续时间 :格式化为
X.Xs
10.8.3. 工具失败检测
_detect_tool_failure() 检查工具结果是否表示失败:
terminal:检查exit_code是否非零,显示[exit N]memory:检查是否超出限制,显示[full]通用:检查
"error"/"failed"关键词,显示[error]
失败的工具调用会以红色前缀显示。
10.9. 内联 Diff 系统
当 Agent 编辑文件时,Hermes CLI 会在工具输出下方直接显示 unified diff 预览,
让用户即时看到文件变更。这是通过 LocalEditSnapshot 和 extract_edit_diff()
实现的。
10.9.1. LocalEditSnapshot
在工具执行 之前 ,capture_local_edit_snapshot() 会记录目标文件的当前内容:
@dataclass
class LocalEditSnapshot:
paths: list[Path] = field(default_factory=list)
before: dict[str, str | None] = field(default_factory=dict)
支持的工具有:
write_file:快照目标路径patch:快照目标路径skill_manage:快照 skill 相关文件(create/edit/patch/write_file/remove_file/delete)
工具执行 之后 ,extract_edit_diff() 比较快照和当前文件内容,
生成 unified diff。
10.9.2. 皮肤感知的 ANSI 渲染
diff 的颜色由皮肤系统控制。_diff_ansi() 函数从活跃皮肤中提取颜色:
元素 |
皮肤颜色键 |
默认值 |
|---|---|---|
文件头 |
session_label |
紫色 #180;160;255 |
Hunk 头 |
session_border |
灰色 #120;120;140 |
删除行(-) |
ui_error(半透明背景) |
深红背景 |
新增行(+) |
ui_ok(半透明背景) |
深绿背景 |
上下文行 |
banner_dim |
灰色 #150;150;150 |
diff 输出有截断保护:最多显示 6 个文件、80 行 diff。超出部分会显示省略摘要。
10.10. 交互模式
Hermes CLI 支持多种交互模式,每种都针对特定的用户交互需求。
10.10.1. Clarify(澄清)
当 Agent 需要用户做出选择时,触发 clarify 回调:
def clarify_callback(cli, question, choices):
timeout = CLI_CONFIG.get("clarify", {}).get("timeout", 120)
response_queue = queue.Queue()
is_open_ended = not choices
cli._clarify_state = {
"question": question,
"choices": choices,
"selected": 0,
"response_queue": response_queue,
}
cli._clarify_deadline = time.monotonic() + timeout
# 阻塞等待用户响应
while True:
try:
result = response_queue.get(timeout=1)
return result
except queue.Empty:
if time.monotonic() > cli._clarify_deadline:
break
# 超时:让 Agent 自行决定
return "The user did not provide a response. Use your best judgement."
关键特性:
方向键导航 :上下键在选项之间移动,选中项高亮显示
"Other" 选项 :用户可以切换到自由文本输入模式
超时机制 :默认 120 秒,超时后 Agent 自行决定
实时倒计时 :UI 显示剩余时间
10.10.2. Approval(审批)
当 Agent 尝试执行危险命令时,触发 approval 回调:
选项 |
效果 |
说明 |
|---|---|---|
once |
本次允许 |
仅当前命令 |
session |
本次会话允许 |
同一命令在当前会话中不再询问 |
always |
永久允许 |
写入永久白名单 |
deny |
拒绝 |
不执行命令 |
view(可选) |
查看完整命令 |
命令超过 70 字符时自动出现 |
approval 回调使用 _approval_lock 序列化并发请求(例如来自并行委派子任务的请求),
确保每个提示都有自己的处理轮次。
10.10.3. Secret(密码输入)
Secret 输入有两种模式:
TUI 模式 (有 _app):通过 prompt_toolkit 的 PasswordProcessor 隐藏输入,
response_queue 阻塞等待。输入缓冲区在进入密码模式前被清空,
防止残留的草稿被误提交。
Fallback 模式 (无 _app):使用标准库的 getpass.getpass() 。
Secret 值通过 save_env_value_secure() 存储到 ~/.hermes/.env ,
从不暴露给模型 。
10.11. 状态栏三层自适应
状态栏是 CLI 底部的一行信息条,显示模型名称、上下文使用量和会话时长。 它有三层自适应布局:
层级 |
终端宽度 |
显示内容 |
|---|---|---|
Narrow |
< 52 列 |
|
Medium |
52 - 75 列 |
|
Wide |
>= 76 列 |
|
Wide 布局额外显示:
上下文进度条 :
████████░░可视化上下文使用率Token 统计 :输入 token、输出 token
费用估算 :基于当前模型的定价
压缩次数 :上下文被压缩的次数
10.11.1. 颜色编码
上下文使用率的颜色编码:
使用率 |
样式类 |
视觉含义 |
|---|---|---|
< 50% |
status-bar-good |
绿色:充裕 |
50% - 80% |
status-bar-warn |
黄色:注意 |
80% - 95% |
status-bar-bad |
橙色:紧张 |
>= 95% |
status-bar-critical |
红色:危急 |
10.12. Ctrl+C 分级处理
Ctrl+C 在 Hermes CLI 中有五级优先级的处理逻辑:
级别 |
条件 |
行为 |
目的 |
|---|---|---|---|
1 |
在交互模式(clarify/approval/sudo/secret)中 |
取消当前交互,返回默认值 |
允许用户退出不想回答的提示 |
2 |
Agent 正在运行 |
中断 Agent 执行(设置 |
停止正在进行的工具调用 |
3 |
空闲状态,距离上次 Ctrl+C < 1 秒 |
退出 REPL(设置 |
双击 Ctrl+C 快速退出 |
4 |
空闲状态,距离上次 Ctrl+C < 3 秒 |
显示确认退出提示 |
防止误触 |
5 |
其他情况 |
记录时间戳,不做任何事 |
重置超时计时 |
这个分级系统确保了:
在 紧急情况下 (Agent 运行中),第一次 Ctrl+C 就能中断执行
在 空闲状态下 ,需要双击才能退出,防止误触
在 交互模式中 ,Ctrl+C 优雅地取消当前提示,而不是退出整个程序
10.13. 扩展 CLI 命令生态
随着 Hermes Agent 功能的不断丰富,CLI 层面陆续引入了多个重量级子系统。 它们不再只是简单的 slash 命令,而是拥有独立数据模型、持久化存储和复杂交互逻辑的 自治模块 。 本节将逐一剖析这些扩展命令的设计理念和架构要点。
10.13.1. Kanban 看板系统
hermes kanban 是一个功能完备的任务管理看板,由三个核心模块支撑:
hermes_cli/kanban.py(约 2000 行)——看板 CLI 的主入口和交互逻辑hermes_cli/kanban_db.py(约 4000 行)——基于 SQLite 的持久化存储层hermes_cli/kanban_diagnostics.py(约 650 行)——任务健康诊断引擎
核心能力 :
任务生命周期管理 :创建(
hermes kanban create)、分配(assign)、 状态流转(move)、关闭(close)——完整的任务生命周期覆盖。看板仪表盘 :以列式布局展示各阶段任务,支持按优先级、负责人、标签筛选。 仪表盘通过 WebSocket 推送实时更新,无需手动刷新。
诊断引擎 :
kanban_diagnostics.py实现了一套任务"压力信号"检测机制—— 当任务长时间未推进、依赖阻塞或被反复重开时,系统会主动发出告警, 帮助团队及时发现和化解瓶颈。
flowchart TD
CLI["hermes kanban <subcmd>"] --> CMD["kanban.py<br/>命令解析与交互"]
CMD --> DB["kanban_db.py<br/>SQLite 持久化"]
CMD --> DIAG["kanban_diagnostics.py<br/>健康诊断"]
DB --> SQLITE["~/.hermes/kanban.db"]
DIAG --> SIGNALS["压力信号检测"]
SIGNALS --> NOTIFY["告警通知"]
Kanban 看板系统架构
10.13.2. Curator 内容管理
hermes curator 提供了两个子命令,用于管理 Skill 和捆绑内容的生命周期:
hermes curator archive——将不再活跃的 Skill 归档,释放索引空间但保留历史记录hermes curator prune——清理过期或冗余的内容条目
Curator 的设计哲学是 轻量但有序 :Agent 的 Skill 仓库会随着时间膨胀, 如果没有定期整理机制,搜索和加载效率都会下降。 Curator 通过标签和最后使用时间自动判断哪些内容值得保留、哪些可以归档或删除。
10.13.3. Voice 语音系统
hermes_cli/voice.py (约 734 行)实现了完整的语音输入输出能力:
语音输入(STT) :支持多种语音识别提供者,将用户语音实时转写为文本
语音输出(TTS) :支持多种 TTS 引擎,可将 Agent 回复合成为语音播放
豆包语音集成 :内置对字节跳动 Doubao Speech 的支持,提供低延迟的中文语音识别与合成
语音模式可通过 /voice slash 命令或 Ctrl+B 快捷键切换。
每个语音提供者独立配置,用户可以在 config.yaml 中选择偏好的 TTS/STT 引擎。
10.13.4. Hooks 钩子系统
hermes_cli/hooks.py (约 385 行)管理 Shell 钩子,让用户可以将 Hermes 的行为
自动化嵌入到 Shell 工作流中:
会话生命周期钩子 :在会话开始、结束时自动执行预定义脚本
事件触发钩子 :响应 Agent 特定事件(如工具调用完成、任务状态变更)
注册与管理 :通过
hermes hooks add/hermes hooks remove/hermes hooks list管理已注册的钩子
钩子系统的核心思想是让 Agent 的能力 延伸到终端环境—— 例如,在每次会话结束时自动推送摘要到 Slack,或在特定工具调用后触发文件同步。
10.13.5. Goals 目标追踪
hermes_cli/goals.py (约 535 行)实现了一个目标追踪与进度管理系统:
目标创建 :定义长期或短期目标,关联里程碑和截止日期
进度追踪 :自动或手动更新目标的完成进度
聚合视图 :以 dashboard 形式展示所有活跃目标的状态
目标系统与 Kanban 看板形成互补——Kanban 管理具体任务的流转, Goals 则从更高维度追踪战略目标的推进情况。
10.13.6. Onboarding 新手引导
agent/onboarding.py 为首次使用 Hermes 的用户提供交互式引导流程:
环境检测 :自动检查 Python 版本、必要的依赖、API Key 配置
交互式配置 :引导用户完成
config.yaml的关键配置项快速体验 :配置完成后自动启动一次示范会话,让用户快速上手
新手引导在用户首次执行 hermes 命令时自动触发,
也支持通过 hermes setup 手动重新运行。
10.13.7. Account Usage 用量追踪
agent/account_usage.py 提供跨 Provider 的 API 用量追踪能力:
多 Provider 聚合 :统一展示来自 Anthropic、OpenAI、OpenRouter 等不同提供商的消费
模型级别统计 :按模型拆分 token 用量和费用估算
用量预警 :接近配额上限时主动提醒
通过 /usage slash 命令可以在 REPL 中实时查看当前会话和历史会话的用量统计。
10.13.8. 其他 CLI 改进
除了上述重量级子系统外,CLI 层面还有若干值得关注的改进:
Slack CLI (hermes_cli/slack_cli.py):提供 Slack 集成的专用命令,
支持从终端直接向 Slack 频道发送消息或拉取对话上下文。
Fallback 命令 (hermes_cli/fallback_cmd.py):管理后备 Provider 的配置。
当主 Provider 不可用时,系统自动切换到预配置的备选方案,确保服务连续性。
Skills Reset (hermes skills reset):新增的子命令,
允许用户将 Skill 回退到默认状态,清除自定义修改。
Model Catalog 改进 (hermes_cli/model_catalog.py):
引入 list_picker_providers 函数和基于 config.yaml 的模型别名解析机制,
使 /model 命令的模型列表更加智能——自动识别用户配置的别名,
按 Provider 分组展示,并标记推荐模型。
Startup Tips :新增约 100 条 CLI 启动提示, 在 REPL 启动时随机展示,涵盖快捷键技巧、隐藏功能、最佳实践等。
10.14. 源码文件索引
本章涉及的主要源文件:
cli.py—HermesCLI类,REPL 主循环,输入路由,状态栏,Ctrl+C 处理hermes_cli/main.py— CLI 入口点,argparse 子命令解析,profile 覆盖hermes_cli/commands.py—CommandDef数据类,COMMAND_REGISTRY,别名解析,Tab 补全hermes_cli/callbacks.py— Clarify、Secret、Approval 交互回调hermes_cli/skin_engine.py—SkinConfig,8+ 内置主题,颜色继承,prompt_toolkit 样式桥接agent/display.py—KawaiiSpinner,工具预览,内联 diffhermes_cli/kanban.py— Kanban 看板 CLI 入口,任务管理交互hermes_cli/kanban_db.py— Kanban SQLite 持久化存储层hermes_cli/kanban_diagnostics.py— 任务健康诊断引擎hermes_cli/curator.py— Curator 内容归档与清理hermes_cli/voice.py— 语音输入输出系统(STT/TTS)hermes_cli/hooks.py— Shell 钩子注册与管理hermes_cli/goals.py— 目标追踪与进度管理hermes_cli/slack_cli.py— Slack 集成命令hermes_cli/fallback_cmd.py— 后备 Provider 管理hermes_cli/model_catalog.py— 模型目录与别名解析agent/onboarding.py— 新手引导流程agent/account_usage.py— 跨 Provider 用量追踪