Composio CLI 命令设计规范:输出流契约、交互策略与退出码的完整工程指南
【免费下载链接】composioComposio powers 1000+ toolkits, tool search, context management, authentication, and a sandboxed workbench to help you build AI agents that turn intent into action.项目地址: https://gitcode.com/GitHub_Trending/co/composio
本篇指南基于 Composio 仓库中.agents/skills/cli-command/references/design.md编写,系统讲解@composio/cli命令面的设计准则:stdout/stderr 双流契约、ui.output()的机器输出边界、TTY 感知的交互策略、环境变量配置体系与退出码语义。读完本文,你将掌握在 Composio 中新增或审查 CLI 命令时应当遵循的完整设计规范,并能对照 ts/packages/cli 的源码理解每条规则背后的实现原理。
一、设计原则:把"人看的"和"机器读的"彻底分开
Composio CLI 的第一条设计原则,是 Unix 传统中"数据与装饰分离"的严格化:stdout 只承载数据,stderr 只承载人类可读的装饰信息。这是整个命令面设计的基石,其余规则几乎都由此派生。
具体落到实现上,规范要求:
ui.output()是唯一的机器数据出口——只有脚本需要捕获的值(API Key、版本号、工具列表等)才通过它写入 stdout;- 保持 quiet / piped 模式干净——当 stdout 被重定向到管道或文件时,终端上不能出现任何多余字符污染数据;
- 优先使用 flags 而非模糊的位置参数——命令的可读性与自文档性优先;
- 统一使用约定俗成的 flags——当命令形态需要时,一致地使用
--json、--dry-run、--force、--no-input、--no-browser,避免每个命令各自发明一套; - 绝不通过 flags 接收 secrets——API Key 等敏感信息不能出现在命令行参数中(进程列表可见),只能走交互输入或环境变量。
在 ts/packages/cli/AGENTS.md 的 "Output Conventions" 一节中,这一原则被进一步细化为三条独立契约:canPrompt(stdin.isTTY && stderr.isTTY)、机器输出(!stdout.isTTY)与canDecorate(stderr.isTTY)。三者的关键约束是:管道(piping)永远不能改变提示或认证行为——composio login | tee应当与人工登录完全等价;同样,重定向 stdin 或 stderr 也绝不能导致数据泄漏到可见的 stdout 终端上。
二、帮助与错误:把失败也当作产品体验的一部分
规范对帮助文本与错误处理提出了明确的要求:
帮助文本就是用户体验(Help text is user experience)。每个命令的帮助应当以简洁的描述开头,并紧跟常见用法示例,让用户在不读源码的情况下就能完成 90% 的操作。
预期错误要给出下一步行动。当用户输错了 flag、缺少参数或鉴权失败时,错误信息不仅要说明"发生了什么",还要告诉用户"下一个命令或修复方法是什么"。例如在 cli-main.ts 中,当出现ValidationError时,CLI 会解析出"Received unknown argument"之类的具体错误,并追加诸如Tip: --xxx requires a value, e.g. --xxx "value"的修复提示,同时打印对应命令的帮助文本。
意外错误要保留调试细节。未预期的异常不应当被吞掉或只显示一行笼统信息,而是通过仓库自有的effect-errors/机制(source-mapped 堆栈、Effect span 时间线、格式化输出)呈现,方便上报与定位。在 cli-main.ts 中可以看到,最终兜底的Effect.catchAll会调用captureErrors/prettyPrintFromCapturedErrors输出错误详情,并设置退出码 1。
三、交互性:只在人类在场时提问
Composio CLI 的交互策略是"严格 TTY 感知"的:
- 统一使用
@clack/prompts,且必须经过现有的 CLI UI 抽象层——即TerminalUI服务(src/services/terminal-ui.ts),而不是在命令里直接调用 Clack; - 仅当 stdin 是 TTY 时才发起提示——自动化环境(CI、管道、脚本)没有键盘,也不该被挂起;
- 非交互模式应当以可操作的错误信息失败,而不是挂起等待——
--no-input或非 TTY 场景下,命令要么使用默认值继续,要么明确报错并给出修复路径。
在 terminal-ui.ts 中,TerminalCapabilities把canPrompt定义为stdinIsTTY && stderrIsTTY:stdin 必须能接收输入,同时 stderr 必须能显示 Clack 提示框。值得注意的是,stdout 是否 TTY 完全不参与提示决策——这保证了composio login | tee场景下提示与认证流程不变。当提示不可用时,ui.confirm直接返回defaultValue(默认true),ui.select返回第一个选项的值,保证脚本流程不被阻塞。
四、配置与状态:环境变量、用户态与项目作用域三层体系
规范明确了 CLI 的配置读取与状态存放规则,这是最容易踩坑的部分:
4.1 环境变量:COMPOSIO_前缀与豁免例外
运行时配置通过effect/Config从环境变量读取,相关代码在 src/services/config.ts 与 src/cli-config.ts:
- 常规键采用大写蛇形命名(upper snake case)并以
COMPOSIO_为前缀,读取时前缀会被剥离。例如设置COMPOSIO_USER_API_KEY=xxx,代码中通过Config.string('USER_API_KEY')读取; - 例外情况:
DEBUG_OVERRIDE_*和FORCE_*两类变量原样读取、不做前缀映射(见 src/constants.ts 中的APP_ENV_CONFIG_KEY_PREFIX与DEBUG_OVERRIDE_ENV_CONFIG_KEY_PREFIX,以及 config.ts 的extendConfigProvider实现)。
这种"前缀剥离"设计的好处是:代码内部只关心短键名,而环境变量命名空间由COMPOSIO_统一隔离,避免与用户机器的其他环境变量冲突。
4.2 用户态:~/.composio/user-config.json
持久化的用户 / 认证状态存放在~/.composio/user-config.json(对应 constants.ts 中的USER_CONFIG_FILE_NAME,其值来自@composio/core的USER_DATA_FILE_NAME)。
关键的合并规则是:ComposioUserContext服务会把环境变量叠加在存储文件之上,且环境变量优先——同一个键,只要环境变量存在,就以环境变量为准覆盖文件中的旧值。这样既支持纯环境变量驱动的无状态部署,也支持交互式登录后的持久化复用。用户态的实现位于 src/services/user-context.ts,API Key 还会优先写入系统 keyring(macOS Keychain / Linux Secret Service,经由@composio/cli-keyring),仅在 keyring 不可用时回退为明文存储。
4.3 项目作用域:项目本地.composio/目录
与全局用户态相对,项目级数据(project-scoped data)使用项目本地的.composio/目录(constants.ts 中PROJECT_COMPOSIO_DIR = '.composio'),其中包含project.json项目配置与.env项目级环境覆盖。这套"全局用户态 + 项目本地态"的分层,让同一个 CLI 可以在多个项目间切换而不互相污染。
五、退出码:只有 0 和 1,中断不算失败
退出码语义是脚本化集成的契约,规范给出的规则非常明确:
- 成功退出
0,失败退出非零(1); - 中断(interrupt,如 Ctrl+C 触发的信号)不视为失败——被中断的命令不应被当作错误处理,也不应污染后续的自动化判断;
- 不存在独立的"无效用法(invalid usage)"退出码——不要自行发明
2之类的特殊码。
上述逻辑落在 src/cli-main.ts 的teardown中:当Exit是失败且Cause不仅仅是中断时,才返回1,否则返回0;同时尊重composio run这类代理进程通过process.exitCode上报的状态。整个 CLI 通过BunRuntime.runMain({ teardown })接入该清理逻辑。
这条规则的意义在于:任何依赖退出码做条件判断的脚本,都可以放心地依据0/ 非0二值语义编写,无需区分"用法错误"与"运行失败"。
六、源码佐证:从规范到实现的关键路径
为了便于在仓库中按图索骥,下表汇总了上述规范对应的核心实现文件:
| 规范主题 | 核心实现 |
|---|---|
| 命令面与整体架构 | ts/packages/cli/AGENTS.md(含完整命令清单与分层结构) |
| 输出流契约 / TerminalUI | src/services/terminal-ui.ts(TerminalCapabilities、ui.output()的stdoutIsTTY判断) |
| 环境变量配置读取 | src/services/config.ts(ConfigProvider.fromEnv+ 前缀映射)、src/cli-config.ts(showBuiltIns: false、isCaseSensitive: true) |
| 前缀与常量定义 | src/constants.ts(COMPOSIO_/DEBUG_OVERRIDE_前缀、缓存文件名、项目目录) |
| 退出码 / 中断语义 | src/cli-main.ts(teardown、BunRuntime.runMain、错误兜底输出) |
| 用户态合并 | src/services/user-context.ts(ComposioUserContext、keyring 回退) |
| 引导装配 | src/bin.ts(Effect 层组合与根命令启动) |
此外,规范指向两份辅助材料:命令的实现范式(Command.make+Effect.gen、Effect 平台边界、必跑的pnpm typecheck与pnpm --filter @composio/cli test)见 .agents/skills/cli-command/references/implementation.md;而 Effect 与 Clack 的只读源码位于 ts/vendor 目录,供实现时对照查阅。
七、实战建议:新增命令时的自检清单
将本规范浓缩为一份可执行的检查清单,供在ts/packages/cli/src/commands/下新增命令或审查既有命令时逐条核对:
- 数据出口:该命令是否产生脚本应捕获的值?是 → 用
ui.output(value)写 stdout,配合ui.log.*/ui.note()做 stderr 装饰;否 → 只做装饰,不写 stdout; - 流契约:绝不向 stderr 写数据、向 stdout 写装饰;绝不根据 stdout 的 TTY 状态分支程序行为(如认证路径);
- flags 一致性:需要时使用
--json/--dry-run/--force/--no-input/--no-browser,避免自定义别名; - secrets:API Key 等敏感输入走环境变量或交互提示,绝不通过 flag 传递;
- 帮助文本:以简洁描述 + 常见示例开头;
- 错误处理:预期错误给出"发生了什么 + 下一步命令";意外错误保留调试细节;
- 交互:提示必须经由
TerminalUI抽象;非 TTY 下不挂起,回退默认值或报可操作错误; - 配置:运行时配置走
effect/Config+COMPOSIO_前缀;用户态存~/.composio/user-config.json;项目态存项目本地.composio/; - 退出码:成功
0、失败1,中断不算失败,不引入额外的"无效用法"码。
遵循这份规范,Composio CLI 的每个命令都能同时服务好两类读者:终端前的人类用户与管道另一端的脚本——这也是该设计文档作为cli-commandskill 核心参考(见 .agents/skills/cli-command/SKILL.md)被反复引用的原因。
【免费下载链接】composioComposio powers 1000+ toolkits, tool search, context management, authentication, and a sandboxed workbench to help you build AI agents that turn intent into action.项目地址: https://gitcode.com/GitHub_Trending/co/composio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考