CLI Style 设计草案
本文是一份讨论稿,用来解释 ChatStyle 想统一什么、不统一什么,以及为什么这些约定对 ChatArch 后续的 chatxxx 工具有价值。它不是最终 API 清单,而是用来避免 X-Y 问题的设计地图。
背景和意图
ChatArch 是一个用于沉淀各类工具的 GitHub 组织。后续会出现很多 chatxxx 形式的 CLI 项目,例如面向发布、文档、网络、证书、数据处理或开发流程的工具。
这些 CLI 项目通常由 chattool pypi / chatpypi init 初始化。ChatStyle 的角色是在模板初始化阶段成为默认依赖,让新工具天然继承同一套 CLI 行为规范,而不是在每个项目里复制交互代码。
这些工具的业务不同,但 CLI 交互问题高度重复:
- 参数缺失时是否补问。
- 什么情况下允许进入交互。
- 自动化环境如何禁止 prompt。
- token、password、API key 如何展示和输入。
- select、checkbox、confirm 如何表现。
- 默认值来自哪里,prompt 里显示什么。
- 错误信息如何让用户知道下一步怎么做。
- 新项目通过
chatpypi init初始化时如何默认获得一致体验。
ChatStyle 的核心目标不是做一个复杂 TUI 框架,也不是抽象所有可能的 CLI 框架能力,而是给 ChatArch 系列 CLI 工具提供一组小而稳定的公共交互规范和 runtime helper。只要某个能力不能被多个 chatxxx 工具共同复用,就不应该进入 ChatStyle。
一句话定义
ChatStyle 是 ChatArch CLI 工具的交互规范和轻量 runtime。
它应该让下游项目可以说:
我只负责业务逻辑;CLI 缺参补问、交互开关、敏感值显示、多选和通用输出都按 ChatStyle 来。
设计目标
统一行为
同一组织下的 CLI 工具应该在常见场景里表现一致:
-i强制当前命令交互。-I禁止交互,适合脚本和 CI。- 缺少可恢复参数时,TTY 可用才自动补问。
- 非 TTY 不阻塞等待输入。
- secret 展示永远 mask。
- select/checkbox 的交互方式一致。
降低模板复杂度
chatpypi init 生成新项目时,不应该复制一大段 prompt 和 resolver 代码。模板只需要依赖:
然后在命令里使用 ChatStyle 的公共 API。
保持基础库通用
ChatStyle 不应该知道 ChatTool、某个 API、某个配置文件路径或某个业务命令。它只提供可复用的 CLI 交互积木。
保持自动化友好
ChatArch 工具经常会被脚本、CI、GitHub Actions 或 cron 使用。交互体验不能牺牲自动化稳定性。
因此所有 prompt 能力都必须有非交互路径:传齐参数、使用默认值、或者快速失败。
非目标
ChatStyle 不做这些事情:
- 不做完整 TUI 应用框架。
- 不接管业务命令流程。
- 不读取或写入下游项目配置。
- 不执行安装、系统命令、
sudo或远端 API。 - 不定义 ChatTool 专属文案。
- 不保证每个项目的视觉品牌都完全一样。
ChatStyle 统一的是“CLI 行为和基础表现”,不是所有业务体验。判断一个能力是否应该进入 ChatStyle 的标准是:它是不是 ChatArch 系列 CLI 工具共同需要的规范,而不是某个工具的一次性便利函数。
概念模型
ChatStyle 可以拆成四层:
User Intent
↓
Command Input Contract
↓
Interaction Runtime
↓
Presentation Primitives
↓
Business Logic
User Intent
用户表达意图有三种方式:
- 显式参数:
--name demo --token xxx - 交互回答:缺参后 prompt 输入
- 默认值:命令、配置或环境提供的默认值
ChatStyle 只负责把这些输入合并成一组完整 values,不决定业务如何执行。
Command Input Contract
命令输入契约描述“这个命令需要哪些字段”。当前对应 CommandSchema:
CommandField:字段名、prompt 文案、类型、默认值、是否 required、是否 sensitive。CommandConstraint:跨字段约束。CommandSchema:一个命令的字段集合和约束集合。
它的作用是让缺参补问变成声明式,而不是每个命令手写一套 if/else。
Interaction Runtime
Interaction runtime 处理“什么时候 prompt、如何失败、如何合并输入”:
interactive=True:用户明确要求交互。interactive=False:用户明确禁止交互。interactive=None:自动模式。- TTY 可用:可以补问。
- TTY 不可用:不能补问,必须快速失败或使用已有默认值。
Presentation Primitives
Presentation primitives 是最底层的交互和展示积木:
- text/path input
- confirm
- select
- checkbox
- secret mask
- heading/note/status
- command suggestion
- priority chain
这些能力应保持业务中立。
Business Logic
业务逻辑属于下游项目,例如:
- 创建 GitHub PR。
- 发布 PyPI 包。
- 生成证书。
- 修改 DNS。
- 写配置文件。
- 调用某个 API。
ChatStyle 不进入这一层。
缺参数约定
缺参数处理是 ChatStyle 的核心价值之一。
推荐规则:
- 可恢复参数不要在 Click option 上设置
required=True。 - 使用
CommandField(required=True)声明业务上必需的字段。 - callback 内调用
resolve_command_inputs()。 - resolver 根据 interactive 策略决定补问或失败。
示例:
import click
from chatstyle import CommandField, CommandSchema, add_interactive_option, resolve_command_inputs
SCHEMA = CommandSchema(
name="publish",
fields=(
CommandField("package", prompt="package name", required=True),
CommandField("repository", prompt="repository", default="pypi"),
CommandField("token", prompt="api token", sensitive=True, required=True),
),
)
@click.command()
@click.option("--package", required=False)
@click.option("--repository", required=False)
@click.option("--token", required=False)
@add_interactive_option
def publish(package, repository, token, interactive):
values = resolve_command_inputs(
schema=SCHEMA,
provided={"package": package, "repository": repository, "token": token},
interactive=interactive,
usage="Usage: publish --package TEXT [--repository TEXT] [--token TEXT] [-i|-I]",
)
run_publish(values)
行为预期:
- 参数齐全:直接执行。
- 参数缺失且 TTY 可用:自动补问可恢复字段。
- 参数缺失且
-i:强制补问;无 TTY 则失败。 - 参数缺失且
-I:不补问,直接失败。 - 参数缺失且非 TTY:不补问,直接失败。
Interactive 开关
ChatStyle 统一三态 interactive:
| 状态 | 来源 | 含义 |
|---|---|---|
True |
-i / --interactive |
强制进入当前命令交互流程 |
False |
-I / --no-interactive |
禁止 prompt,适合自动化 |
None |
未指定 | 自动模式,只有 TTY 可用且缺少可恢复字段时 prompt |
这里有两个重要边界:
-i不是“进入全局向导”,只作用于当前命令。-I是自动化契约,必须保证不会阻塞。
Mask 和敏感值
敏感值包括:
- token
- password
- API key
- app secret
- webhook secret
- private key 片段
约定:
- 输入时隐藏。
- 展示时 mask。
- 日志、异常、summary 不输出原文。
- 如果空输入表示保留旧值,prompt 必须明确说明。
例子:
Choice 和多选
ChatStyle 提供统一的 choice 表达,不要求下游项目直接依赖 questionary 的细节。
原则:
questionary可用时提供更好的交互体验。- 不可用时 fallback 到 Click。
- 选项的 label 给人看,value 给程序用。
- 空选项不能变成业务异常,应由调用方决定如何处理。
- checkbox 支持多选,但不应膨胀成复杂 TUI 状态机。
输出风格
ChatStyle 的输出层应该提供通用表现,而不是业务解释。
合理的通用能力:
- heading
- note
- success/warning/error/info status
- key-value summary
- suggested commands
- priority chain
需要谨慎的能力:
- setup 专属 helper
- 某个业务命令专属 summary
- 自动执行系统命令
- 带业务语义的错误恢复建议
关于 setup 的重新理解
“setup 阶段输出、建议命令、配置优先级展示”最初来自 setup 命令场景,但这些能力并不是 setup 独有。
更合理的抽象是:
- 流程阶段展示:任何长流程命令都需要。
- 建议命令展示:doctor、deploy、repair、setup 都可能需要。
- 配置优先级展示:所有读取配置的 CLI 都可能需要。
因此实现上应避免给 setup 单独开小灶。当前方向是把它们归入通用 output/flow 能力:
render_stage("Check environment")
render_suggested_commands(["sudo systemctl restart demo"])
render_priority_chain(["CLI option", "ENV", "config file", "default"])
当前不把 chatstyle.setup 作为第一版公共核心;setup 类需求由通用 flow/output helper 承载。
ChatStyle 和 chattool pypi
ChatStyle 的主要落点是配合 chattool pypi / chatpypi init 创建 ChatArch 系列 CLI 工具。模板阶段应该只生成最少代码:
- 添加
chatstyle依赖。 - 使用 Click 作为命令框架。
- 使用
CommandSchema描述输入。 - 使用
add_interactive_option()提供-i/-I。 - 使用
resolve_command_inputs()完成缺参补问。 - 使用
mask_secret()、select/checkbox helper 处理常见交互。
模板不应该复制 ChatStyle 内部实现。
API 分层建议
稳定公共 API
适合放在 chatstyle.__init__:
CommandFieldCommandSchemaCommandConstraintresolve_command_inputsadd_interactive_optionask_textask_pathask_confirmask_selectask_checkboxmask_secret
候选公共 API
需要进一步讨论命名和边界:
render_headingrender_noterender_statusrender_suggested_commandsrender_priority_chainrender_flow_startrender_stagerender_successrender_warningrender_failurerender_commandscreate_choiceget_separator
不应公开或应谨慎公开
- 下划线 helper。
- 绑定某个业务场景的 helper。
- 需要可选依赖对象作为参数的 API。
- 会执行系统命令或写文件的 API。
模块边界草案
建议长期模块边界:
chatstyle.input 输入契约、解析、Click 集成
chatstyle.tui prompt 原语、choice 表达和 adapter
chatstyle.render 通用输出、流程阶段、建议命令、优先级链
chatstyle.security 敏感值显示和输入
chatstyle.core TTY / interactive 策略、错误 helper、共享常量
chatstyle.patterns 跨模块组合模式
这里 flow 是核心流程展示模块;setup 类场景通过 flow / output 组合实现。
设计原则
小核心
宁愿 API 少一点,也不要把单个项目的便利函数做成基础库公共契约。
可降级
Rich、questionary、prompt_toolkit 都是体验增强,不是运行前提。
自动化优先
所有交互能力都必须能被显式参数或 -I 绕过。
声明式优先
缺参补问和默认值尽量由 schema 描述,不在每个 command callback 里复制 if/else。
业务外置
ChatStyle 不知道“发布、证书、DNS、PR、setup”具体意味着什么。
待讨论问题
- 是否需要在未来重新引入
chatstyle.setup作为场景 wrapper? - 输出层应该提供哪些稳定 API:只提供 heading/note,还是也提供 status/summary/table?
CommandSchema是否应该支持更丰富的 choice value/label 对象,而不是Sequence[str]?prompt_if_missing的命名是否准确?是否应该改成prompt_if_defaulted或confirm_default?- 错误信息是否需要结构化错误码,方便自动化调用方识别?
chatpypi init模板应该生成多复杂的示例:只展示 text/path/secret,还是也展示 select/checkbox?- 是否需要项目级 preset,例如
standard_cli_schema(),还是保持完全显式? - 文档中是否应该把 ChatStyle 定义为“规范优先,runtime 其次”?
当前建议
短期建议:
- 保留
CommandSchema作为核心。 - 强化 prompt、mask、interactive 和 Click 集成测试。
- 将 setup 场景能力收敛到通用 flow/output API。
- 文档优先讲概念和约定,再讲 API。
chatpypi init只依赖 ChatStyle,不复制实现。
长期建议:
- ChatStyle 作为 ChatArch CLI 行为规范的单一来源。
- 下游项目只通过依赖升级获得交互规范更新。
- 新增 API 必须先回答:这个能力是否属于 ChatArch CLI 公共规范,且至少适用于两个以上
chatxxx工具?