1. 为什么 Codex Skills 需要 click 与 rich 这套组合如果你正在做 Codex Skills 开发大概率会遇到一个很具体的问题技能写完了但调用体验很粗糙。参数靠位置传报错靠 traceback输出是一坨没有结构的字符串。Agent 拿到这种输出要么解析失败要么得写一堆正则去猜。我自己在接第一个查询类 Skill 的时候就因为输出格式不稳定来回改了三四版解析逻辑。click 解决的是“契约”问题。它把命令的参数、类型、默认值、可选范围全部声明清楚Agent 在调用前就能从 help 文本里读到结构化信息。rich 解决的是“呈现”问题。表格、面板、高亮、进度条这些在终端里看起来是美化但对 Agent 来说更重要的是输出边界清晰、字段对齐、状态可读。这篇文章面向的是已经在写 Codex Skills、或者准备把本地脚本封装成 Agent 可调用工具的开发者。我会用一个数据查询 Skill 作为主线从 click 的参数定义讲到 rich 的表格渲染再把它封装成 Skill 函数最后用 TaoToken 的统一 Key 通道把模型调用接进来给出可复制的 config.toml 和 settings.json 骨架以及逐步验证动作。整套流程跑通之后你手里会有一个既能被人用、也能被 Agent 调的 CLI 技能。2. TaoToken 前置统一 Key 与 API 通道准备在把 Skill 接到模型之前需要先有一个稳定的调用通道。TaoToken 在这里的角色是统一 Key 和 API 入口让你不用在多个模型供应商之间来回切换配置。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。你需要先拿到一个 API Key。进入控制台创建 Key 的页面在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建之后复制保存后面配置里会用到。如果你还没决定用哪个模型可以先去模型对话页面试一下 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 确认通道可用再写进配置。对于长期做编码和 Agent 的场景Coding Plan 会更合适入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 配置字段有疑问的时候对照文档查。这里要强调一点TaoToken 是统一的 API 通道不是让你绕过任何合规流程的工具。你拿到的 Key 就是正常调用凭证配置方式跟标准 API 客户端一致。3. 可复制配置config.toml 与 settings.json 骨架Codex Skills 的配置通常分两层一层是模型通道配置一层是 Skill 运行时的设置。下面给出两个可直接复制的骨架。3.1 config.toml 模型通道配置# config.toml # Codex Skills 模型通道配置骨架 [provider] name taotoken base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY # 从环境变量读取不要硬编码 [model] default claude-sonnet-4-20250514 fallback gpt-4o-mini max_tokens 4096 temperature 0.2 [skill] name data_search entry skill.py timeout_seconds 30 retry 2 [output] format json # Agent 模式用 json终端模式用 table pretty true关键点api_key_env指向环境变量不要把 Key 写进文件。base_url用 API 入口不带任何多余路径。temperature在 Skill 场景建议调低保证输出稳定。3.2 settings.json Skill 运行时设置{ skill_name: data_search, version: 1.0.0, runtime: { python: 3.11, dependencies: [click8.1, rich13.0, httpx0.27] }, parameters: { query: { type: string, required: true }, limit: { type: integer, default: 10, max: 1000 }, format: { type: string, enum: [json, csv, table], default: table }, sort_by: { type: string, enum: [id, name, score], default: id } }, channel: { provider: taotoken, base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY } }这两个文件的分工是config.toml 管通道和模型settings.json 管 Skill 自身的参数契约。Agent 读取 settings.json 就能知道这个 Skill 接受什么参数、返回什么格式。3.3 环境变量设置# Linux / macOS export TAOTOKEN_API_KEY你的Key # Windows PowerShell $env:TAOTOKEN_API_KEY你的Key设置完之后用echo $TAOTOKEN_API_KEY确认能读到。这一步没做对后面所有调用都会 401。4. click 参数契约与 rich 输出实现4.1 click 定义命令契约click 的核心是装饰器风格。下面这段定义了一个带子命令的 CLI参数类型和可选范围都声明清楚。import click OUTPUT_FORMATS [json, csv, table] SORT_FIELDS [id, name, score] click.group() def cli(): Codex Skill CLI: 数据查询技能入口。 pass cli.command() click.argument(query, typeclick.STRING, requiredTrue) click.option(--limit, -n, typeclick.INT, default10, help返回结果最大数量。) click.option(--format, -f, typeclick.Choice(OUTPUT_FORMATS), defaulttable, help输出格式。) click.option(--sort-by, -s, typeclick.Choice(SORT_FIELDS), defaultid, help排序字段。) click.option(--skill-mode, is_flagTrue, defaultFalse, helpSkill 模式仅输出结构化数据。) def search(query, limit, format, sort_by, skill_mode): 执行数据搜索。 result data_search_skill(query, limit, format, sort_by) render_output(result, format, skill_mode)click.Choice这一层很关键。Agent 传了非法值click 会在进入业务逻辑之前就拦截并返回明确错误不需要你在函数里写 if-else 校验。4.2 rich 渲染表格与面板rich 的 Console 会自动检测终端能力。表格用Table提示用Panel状态字段用Text上色。from rich.console import Console from rich.table import Table from rich.panel import Panel from rich.text import Text console Console() def render_table(data): table Table(title搜索结果, show_headerTrue, header_stylebold magenta) table.add_column(ID, styledim, width5) table.add_column(Name, stylecyan) table.add_column(Score, justifyright, stylegreen) table.add_column(Status, justifycenter) for row in data: color green if row[status] Active else yellow if row[status] Pending else red table.add_row(str(row[id]), row[name], f{row[score]:.1f}, Text(row[status], stylecolor)) console.print(table) console.print(Panel(f共 {len(data)} 条结果, stylebold blue))Text嵌入单元格做条件着色比整行着色更精确。Panel用来做结果汇总Agent 解析时也能通过面板文本快速拿到总数。4.3 Skill 函数与渲染分离Skill 函数只负责返回结构化数据渲染交给 CLI 层。这样同一套逻辑既能给人用也能给 Agent 用。def data_search_skill(query, limit10, formattable, sort_byid): results get_mock_data() if query: results [r for r in results if query.lower() in r[name].lower()] results.sort(keylambda x: x.get(sort_by, 0), reverseTrue) results results[:limit] if format json: import json return json.dumps(results, indent2, ensure_asciiFalse) elif format csv: import io, csv buf io.StringIO() writer csv.DictWriter(buf, fieldnamesresults[0].keys()) writer.writeheader() writer.writerows(results) return buf.getvalue() else: return {type: table, data: results}注意 table 格式返回的是字典由 CLI 层决定怎么渲染。Agent 模式下走 json 分支直接拿到字符串。5. 验证请求与成功结果配置和代码都就位之后按下面步骤逐步验证。5.1 验证通道连通先用一个最小请求确认 Key 和 base_url 可用。curl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ | head -c 500返回模型列表说明通道正常。如果返回 401检查环境变量是否生效返回 404检查 base_url 是否写成了带多余路径的地址。5.2 验证 CLI 参数解析python skill.py search Alice --limit 3 --format table预期看到一张带颜色的表格Status 列 Active 为绿色Pending 为黄色。如果参数传了非法 formatclick 会直接报错并列出可选值。5.3 验证 Skill 模式输出python skill.py search Alice --format json --skill-mode预期输出纯 JSON没有 rich 的 ANSI 转义码。Agent 拿到这个字符串可以直接json.loads。5.4 验证模型调用链路在 Skill 里加一段调用模型的逻辑用 TaoToken 通道。import httpx, os def call_model(prompt): resp httpx.post( https://taotoken.net/api/v1/chat/completions, headers{Authorization: fBearer {os.environ[TAOTOKEN_API_KEY]}}, json{ model: claude-sonnet-4-20250514, messages: [{role: user, content: prompt}], max_tokens: 512 }, timeout30 ) resp.raise_for_status() return resp.json()[choices][0][message][content]跑一次call_model(用一句话说明 click 的作用)能拿到正常回复就说明整条链路通了。6. 本篇常见错排查报错一click.exceptions.BadParameter。这是 Choice 类型拦截了非法输入。检查 Agent 传参是否在 enum 范围内。如果 Agent 经常传错把 enum 写进 settings.json 的 parameters 里让 Agent 在调用前就能读到约束。报错二rich 输出带 ANSI 转义码导致 JSON 解析失败。原因是 Skill 模式下走了 table 分支。检查--skill-mode是否生效以及 format 是否强制为 json。最稳妥的做法是在 Skill 函数里判断 skill_mode直接返回纯字符串。报错三401 Unauthorized。Key 没读到或者写错了。先echo $TAOTOKEN_API_KEY确认再检查 config.toml 里的api_key_env名称是否和实际环境变量一致。注意不要有多余空格。报错四httpx.ConnectTimeout。通道地址写错或者网络不通。确认 base_url 是https://taotoken.net/api不要带/v1之外的路径。超时时间在 config.toml 的timeout_seconds里调大。报错五表格列宽错乱。rich 的 Table 在窄终端下会自动换行。如果 Agent 解析表格文本建议改用 json 格式不要解析渲染后的表格字符串。报错六ModuleNotFoundError: No module named click。依赖没装。按 settings.json 里的 dependencies 执行pip install click rich httpx。排障过程中如果需要确认 Key 状态去 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 看 Key 是否有效。接入细节对照 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。7. 把 Skill 接到 Codex 工作流Skill 跑通之后下一步是让它进入日常编码流程。如果你主要用 Claude Code 做开发可以参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite 里的接入方式把 Skill 作为工具挂进去。长期做编码和 Agent 的话Coding Plan 的额度模型更适合持续调用入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。模型选择上如果拿不准先去 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 对比几个模型的输出风格再写进 config.toml 的 default 字段。一个实用技巧把 settings.json 里的 parameters 直接喂给 Agent 作为工具描述Agent 就能在调用前知道每个参数的类型和范围减少无效调用。这比在 prompt 里用自然语言描述参数要可靠得多。