跳转到内容

统一 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):

  1. 检查 repo-root canonical path:skills/cli-anything-<software>/SKILL.md
  2. 回退到 packaged path:cli_anything/<software>/skills/SKILL.md
  3. Banner 中显示找到的路径

这使得:

  • Monorepo 开发时:从 skills/ 目录读取,修改即时生效
  • 用户安装后:从 pip 包中的 skills/ 子目录读取

prompt_toolkit 集成

# 伪代码:prompt_toolkit 集成
from prompt_toolkit import PromptSession
from 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 的使用方式