统一 REPL Skin 系统
学习目标
- 理解 REPL Skin 的设计目标和部署模式
- 掌握 ANSI 颜色码系统和可复用 UI 组件
- 了解 SKILL.md 路径自适应逻辑
- 掌握 prompt_toolkit 集成的 REPL 会话
前置知识
本章涉及面向 Agent 的 CLI 接口规范,建议先阅读:
下文直接聚焦 REPL Skin 的具体实现。
项目实践
设计目标
当系统包含 40+ 个 CLI 工具时,用户体验一致性成为关键需求。repl_skin.py(567 行)是所有生成 CLI 共享的统一终端 UI 实现:
- 品牌横幅(名称、版本、SKILL.md 路径)
- 统一提示符格式
- 标准消息颜色
- 表格和进度条
- 告别消息
部署模式:拷贝而非 Import
每个 CLI 拷贝 repl_skin.py 到自身的 utils/ 目录:
gimp/agent-harness/cli_anything/gimp/utils/repl_skin.py ← GIMP 的拷贝blender/agent-harness/cli_anything/blender/utils/repl_skin.py ← Blender 的拷贝原因:每个 CLI 是独立的 pip 包,不应交叉 import。拷贝确保零交叉依赖。
ANSI 颜色码系统
repl_skin.py 使用纯 ANSI 转义码(无外部依赖)实现颜色系统:
# 品牌色_CYAN = "\033[38;5;80m" # cli-anything 品牌青色_CYAN_BG = "\033[48;5;80m" # 背景色_WHITE = "\033[97m"
# 软件强调色(每个软件独立配色)_ACCENT_COLORS = { "gimp": "\033[38;5;214m", # 暖橙 "blender": "\033[38;5;208m", # 深橙 "inkscape": "\033[38;5;39m", # 亮蓝 "audacity": "\033[38;5;33m", # 海军蓝 "libreoffice": "\033[38;5;40m", # 绿色 ...}
# 状态色_GREEN = "\033[38;5;78m" # 成功_YELLOW = "\033[38;5;220m" # 警告_RED = "\033[38;5;196m" # 错误_BLUE = "\033[38;5;75m" # 信息无外部 UI 库依赖——仅使用 Python 内置的 ANSI 转义码。这确保了 CLI 的最小依赖树。
可复用 UI 组件
ReplSkin 类提供的组件:
| 方法 | 效果 |
|---|---|
print_banner() | 品牌启动框(box drawing characters) |
create_prompt_session() | prompt_toolkit 会话(历史 + 样式) |
get_input() | 提示符 tool[ProjectName]*> |
success() | ✓ 绿色消息 |
error() | ✗ 红色消息 |
warning() | ⚠ 黄色消息 |
info() | ● 蓝色消息 |
status() | 键值状态行 |
table() | 格式化表格 |
progress() | 进度条 |
print_goodbye() | 告别消息 |
SKILL.md 路径自适应
print_banner() 中的路径选择逻辑(源码验证 repl_skin.py):
- 检查 repo-root canonical path:
skills/cli-anything-<software>/SKILL.md - 回退到 packaged path:
cli_anything/<software>/skills/SKILL.md - Banner 中显示找到的路径
这使得:
- Monorepo 开发时:从
skills/目录读取,修改即时生效 - 用户安装后:从 pip 包中的
skills/子目录读取
prompt_toolkit 集成
# 伪代码:prompt_toolkit 集成from prompt_toolkit import PromptSessionfrom prompt_toolkit.history import FileHistory
def create_prompt_session(self): history_file = Path.home() / ".cli-anything" / f".{self.software_name}_history" return PromptSession( history=FileHistory(str(history_file)), # ... 样式配置 )命令历史持久化到 ~/.cli-anything/ 目录,不同 CLI 的历史文件独立。
提示符设计
gimp> # 无项目gimp[MyProject]> # 有项目,未修改gimp[MyProject]*> # 有项目,已修改(* 标记)* 标记是关键的 UX 信号——提醒用户有未保存的变更。
路径显示优化
_display_home_path() 函数将绝对路径转换为 ~ 相对路径:
def _display_home_path(path: str) -> str: expanded = Path(path).expanduser().resolve() home = Path.home().resolve() try: relative = expanded.relative_to(home) return f"~/{relative}" except ValueError: return path这使得 Banner 中的 SKILL.md 路径更易读。
问题与规避
| 问题 | 规避策略 |
|---|---|
| 皮肤更新需要重新发布所有 CLI | 拷贝模式是权衡结果——皮肤变更频率低,成本可接受 |
| 不同终端 ANSI 支持不一 | 纯 ANSI 转义码,所有现代终端支持 |
| 无外部依赖限制 UI 复杂度 | 567 行纯 ANSI 实现足够丰富的 UI,无需 rich/colorama |
| 提示符中的项目名含特殊字符 | _strip_ansi() 和 _visible_len() 处理 ANSI 码的长度计算 |
设计取舍
拷贝 vs 共享包
选择:每个 CLI 拷贝 skin 文件。
原因:
- 独立的 pip 包不应交叉依赖
- 拷贝允许各 CLI 在未来独立演进 skin(如 GIMP 需要特殊的图层提示符)
- 皮肤更新频率低(数月一次),重新发布 40+ 包的代价可接受
ANSI vs rich/colorama
选择:纯 ANSI 转义码,不依赖 rich 或 colorama。
原因:
- 零额外依赖,CLI 安装后不引入第三方包
- ANSI 是现代终端的标准支持
- rich/colorama 增加了依赖树和兼容性风险
代价:需要手动处理 ANSI 码的长度计算(_visible_len()),rich 会自动处理。
参考来源
- 源码验证:
repl_skin.py全文(567 行) - 源码验证:各 Harness 中
utils/repl_skin.py的使用方式