我用 Codex 快一年了,重度使用的情况下,每个月花在 Token 上的钱不比一杯咖啡便宜。真正让人焦虑的不是花多少,而是你根本不知道它怎么花掉的——每次对话结束,Codex 只会告诉你这轮用了多少 token,但你要看今天、这个月、哪个项目烧得最狠,基本靠猜。后来我写了一个 Skill,让 Codex 用一句话就能打开每日用量看板,彻底解决了这个黑箱问题。
这篇文章把整个升级过程拆开讲清楚:从为什么用 Skill 而不是外部脚本,到 SKILL.md 怎么设计、统计脚本怎么写、部署时踩了哪些坑,全部记录下来。适合两类人看:一类是天天用 Codex 写代码但没仔细算过成本的人,另一类是打算给 Codex 集成自定义 Skill、想找个完整案例参考的开发者。
1. 为什么你急需一个 Token 统计 Skill
1.1 用量焦虑:看不见的 token 在烧钱
大多数人用 Codex 的方式是“有问题就问”,一条任务可能来回跑几十轮,每轮都会把上下文重新拼进模型里。你以为自己只写了一百行代码,实际上模型把整个项目的目录结构、过往对话、工具输出全部读了一遍——这些都要算 token。Codex 的计费逻辑是按“输入 + 输出”总量走的,上下文越长,单轮成本越高。
更麻烦的是,Codex 的会话记录分散在本地目录里,官方界面只管展示“本次会话用了多少 token”,没有给你一个“今天总共花了多少”的汇总视角。你一个月结束看到账单才发现:咦,怎么超了这么多?那时候已经来不及省了。
我个人的经验是:用量统计必须每天都看,就像记账一样,一旦变成月末回头看,就等着被数字吓一跳。
1.2 为什么选择 Skill 而不是外部脚本
有人可能会说,想统计 token,写个 Python 脚本不就行了?为什么非要绕一圈做成 Skill?
区别在于使用场景。外部脚本你得在终端里手动跑到文件夹、找到正确的文件、执行命令,然后盯着输出看。对技术人来说,这一套不算难,但问题是——你总是会忘记跑。人会偷懒,这是常态。
做成 Skill 之后就不一样了。Skill 的本质是给 Codex 注入一套“行为预设”,让它在特定触发条件下自动执行一组操作。你不需要记住脚本路径,不需要翻历史命令,只要在对话里说一句“打开每日用量看板”,剩下的全部交给 Codex。
还有一个更重要的点:直接让 Codex 读取本地文件、计算数据,往往会出现格式不稳定的问题。用 Skill 把统计逻辑固化下来,每次得到的结果都是同一套口径,不会这次按 session 算、下次按 usage 字段算。对于做记录、做对比来说,口径一致比什么都重要。
1.3 升级前的老方案痛点
我在做这个 Skill 之前,用的是最原始的方式:手动打开 Codex 的会话目录,用jq和awk现场拼命令,把 JSONL 里的 usage 字段捞出来求和。
这套做法有几个明显的问题。首先是麻烦,每次都要回忆一遍 JSONL 的结构,时间一长根本不记得哪个字段代表输入 token、哪个代表输出 token。其次是容易出错,Codex 的会话文件偶尔会写入不完整的 JSON 行,jq一碰到解析失败就整个挂掉,你还得单独处理容错。
最关键的是,手动命令只能告诉你“总数”,给不了“每天的趋势”。而用量管理最需要的就是趋势:周一为什么爆了?是不是那天跑了几个超大任务?这些信息一旦变成历史,再去查就很费劲。
所以升级的方向很清楚:把统计逻辑固化成 Skill,用自然语言触发,输出结构化的每日用量看板。
2. 升级设计:一句话触发的用量看板
2.1 核心思路与交互设计
整个 Skill 的设计目标,我定成了三条硬性要求:
- 只用一句话触发,不需要额外参数
- 统计结果必须按“日期”分组,而不是按会话
- 输出必须直接可读,不能再套一层工具去解析
交互上,我参考了 Claude Code 的 Skill 机制——一个 Skill 对应一个文件夹,里面必须有SKILL.md作为说明书,其他辅助脚本放在同目录下。Codex 读到这里有 Skill 定义,会把这套能力注册进上下文,用户只要提到“用量”“token 统计”这类关键词,就会自动触发。
为什么强调“一句话”?因为使用频率越高的操作,进入门槛越低越好。如果每次都要给 Codex 解释“你去看哪个文件、用什么脚本、输出什么格式”,那和手动敲命令没有区别,Skill 就失去了意义。
2.2 数据从哪里来:本地会话记录解析
Codex 在本地会保存每一条会话记录,通常是 JSONL 格式,一行一条消息事件,里面包含多个字段,其中就有usage对象,记录着input_tokens和output_tokens之类的数据。
这里有个容易混淆的点:不是所有行都有usage。大部分用户消息、工具调用消息都不带用量信息,只有模型响应那一条才携带。所以统计脚本不是把每一行都拿过来累加,而是要识别出“这一行是模型完成响应,并且带 usage 字段”,再把它取出来。
不同操作系统的会话目录路径不一样,这是部署时最容易踩的坑。我把路径探测逻辑直接写在脚本里,优先读取系统环境变量,找不到就按常见默认路径去匹配,这样换了电脑也能直接跑。
2.3 看板展示什么:从总量到每日趋势
我设计的看板分三块内容:
| 区块 | 展示内容 | 解决的痛点 |
|---|---|---|
| 总览 | 总 token 数、总会话数、估算金额 | 快速判断今天是不是“超标”了 |
| 按日分组 | 每日 token 消耗柱状图(终端渲染) | 看趋势,找出峰值日期 |
| 按项目/角色分组 | 不同角色消耗排序 | 判断是编码任务多还是聊天任务多 |
其中最实用的就是“按日分组”。Codex 的会话文件命名通常带有时间戳,可以直接从文件名或文件内首条消息的时间提取日期。按天聚合后,再用简单的print拼字符画柱状图——不需要引入任何第三方图形库,终端里完全够用。
3. 核心实现:SKILL.md 与统计脚本拆解
3.1 Skill 的文件结构与触发词定义
一个完整的 Skill 目录长这样:
~/.codex/skills/token-stats/ ├── SKILL.md ├── token_stats.py └── requirements.txtSKILL.md是这个 Skill 的入口。Codex 通过它识别你安装了什么能力,里面要写清楚:这个 Skill 是干什么的、什么时候触发、怎么用。建议加上若干触发词,让模型更容易命中。
下面是我整理过的SKILL.md内容,省略了部分注释,核心结构如下:
--- name: token-stats description: 统计 Codex Token 用量,按天汇总输出看板 triggers: - 用量 - token 统计 - token 用量 - 每日用量 - usage --- # Token 统计 Skill 当用户请求查看 Codex Token 用量或每日用量看板时,执行本 Skill。 步骤: 1. 定位 Codex 会话记录目录 2. 运行 token_stats.py 3. 将脚本输出的表格直接展示给用户注意里面我特意写了“将脚本输出的表格直接展示给用户”——这是对模型行为的约束。没有这一句,模型很可能自己编一个表格出来,而不是老老实实跑脚本。
3.2 Token 统计字段与计算逻辑
统计脚本是整个 Skill 的核心。下面这个是我的实际实现(略作精简),它做的事情是:遍历会话目录下所有 JSONL 文件,提取带usage字段的消息,按天聚合 token 消耗。
#!/usr/bin/env python3 import json import os from pathlib import Path from collections import defaultdict from datetime import datetime # 可以自定义路径,默认按常见位置查找 HOME = Path.home() CANDIDATE_DIRS = [ Path(os.environ.get("CODEX_SESSIONS_DIR", "")), HOME / ".codex" / "sessions", HOME / ".codex" / "log", ] def find_sessions_dir(): for d in CANDIDATE_DIRS: if d.exists(): return d raise SystemExit("未找到 Codex 会话目录,请检查路径") def parse_date_from_file(path: Path) -> str: """优先从文件名里提取日期,兜底用文件的修改时间""" try: parts = path.stem.split("_") date_part = parts[0] if parts else "" datetime.strptime(date_part, "%Y-%m-%d") return date_part except (ValueError, IndexError): ts = path.stat().st_mtime return datetime.fromtimestamp(ts).strftime("%Y-%m-%d") def main(): sessions_dir = find_sessions_dir() daily = defaultdict(lambda: {"input": 0, "output": 0, "sessions": 0}) total = {"input": 0, "output": 0, "sessions": 0} for path in sessions_dir.glob("*.jsonl"): date = parse_date_from_file(path) daily[date]["sessions"] += 1 try: with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue try: obj = json.loads(line) except json.JSONDecodeError: continue usage = obj.get("usage") if isinstance(usage, dict): inp = usage.get("input_tokens", usage.get("input", 0)) or 0 out = usage.get("output_tokens", usage.get("output", 0)) or 0 daily[date]["input"] += inp daily[date]["output"] += out total["input"] += inp total["output"] += out except Exception as e: print(f"警告:{path.name} 读取失败 - {e}") print("=" * 48) print(f"Codex Token 用量看板(总计 {len(daily)} 天)") print("=" * 48) print(f"总 Token: {total['input'] + total['output']:,}") print(f"输入 Token: {total['input']:,}") print(f"输出 Token: {total['output']:,}") print(f"会话总数: {total['sessions']}") print("-" * 48) print(f"{'日期':<12}{'输入':>12}{'输出':>12}{'合计':>14}") for d in sorted(daily.keys(), reverse=True): row = daily[d] total_day = row["input"] + row["output"] print(f"{d:<12}{row['input']:>12,}{row['output']:>12,}{total_day:>14,}") if __name__ == "__main__": main()这段代码有几个值得说的细节。
第一个是容错。JSONL 文件数量多了之后,偶尔会出现某一行写坏的情况,比如断电、进程被杀。如果不做try/except,脚本会挂在第一个坏行上。这里的选择是“能跳就跳,不影响整体统计”。
第二个是字段兼容性。不同版本的 Codex 对 usage 的字段名并不完全一致,有的叫input_tokens,有的叫input,还有的嵌套在别的地方。我同时兼容了两种最常用的写法,遇到都没有的情况就归零,而不是直接报错。
3.3 看板输出与终端渲染
脚本的输出我特意做成了“冷静但清晰”的风格。第一行是汇总,紧接着是每天的明细表格。列宽对齐用的是 Python 的格式化字符串,>12表示右对齐、宽度 12,数字大一些也不会错位。
有人会问,为什么不直接生成 HTML 或者图片?因为 Skill 的定位是“对话内快速查看”,输出到终端已经足够。真想做可视化排名,可以把脚本改成输出 CSV,再接入自己的报表系统。
我建议不要在 Skill 里塞太重的图形依赖。Python 的rich、plotly是好看,但每次执行都要加载一堆东西,响应速度会变慢,在终端里反而显得累赘。
4. 实操部署:5分钟接入你的 Codex
4.1 手动安装 Skill 到配置目录
部署步骤其实非常简单,核心就是把你的 Skill 文件夹放到 Codex 能找到的位置。
先确认 Codex 配置目录的位置。在终端里执行:
ls ~/.codex正常情况下你能看到sessions、history之类的目录。如果没有skills目录,手动创建一个:
mkdir -p ~/.codex/skills/token-stats然后把SKILL.md和token_stats.py放进去。requirements.txt其实可以省略,因为这个统计脚本只用 Python 标准库,没有任何第三方依赖。保留它只是习惯,方便以后扩展。
提示:如果你用的是 Codex 的容器版或远程开发环境,路径会不一样。先
echo $CODEX_SESSIONS_DIR看环境变量,有就优先用这个。
安装完成后,重启 Codex 会话。不要指望正在运行的会话立刻感知新的 Skill,一定要新开一个会话再测试。
4.2 第一次触发与实测记录
新开会话后,直接输入:
打开每日用量看板如果没有反应,试试触发词更明确的说法:
用 token-stats skill 统计一下 token 用量第一次跑的时候,Codex 可能会先给你一段解释,比如“正在定位会话目录”,然后运行脚本,把表格展示出来。如果一切正常,你会看到类似这样的输出:
================================================ Codex Token 用量看板(总计 7 天) ================================================ 总 Token: 1,234,567 输入 Token: 900,000 输出 Token: 334,567 会话总数: 23 ------------------------------------------------ 日期 输入 输出 合计 2025-06-18 210,000 80,000 290,000 2025-06-17 150,000 55,000 205,000如果出现了这个界面,说明整个链路已经通了,从触发词命中到脚本执行、到结果回传,全部正常。
如果你说了触发词但 Codex 没有跑脚本,而是自己编了一段话,大概率是SKILL.md的写法不够“坚定”。回到文件里把描述改得更直接:在步骤里明确写“必须执行 token_stats.py”,然后重新测一次。
4.3 升级玩法:定时日报与多项目汇总
跑通基础版之后,可以考虑两个升级方向。
第一个是加定时日报。在 macOS 或 Linux 上用cron每天下午六点跑一次统计,把结果输出到文件或者推送到消息机器人。这个需求可以写成第二个 Skill,也可以直接在cron里调用脚本本身。
第二个是分项目汇总。Codex 会话里大部分情况下是一单一会话,但有时候你会开着同一个会话连续做好几个任务。要区分项目,可以在会话目录下再建子目录,然后把不同项目放到不同路径下,脚本里按二级目录聚合即可。
我实际操作下来,最常用的还是“每日看板”,因为趋势一旦能看见,你就知道该收敛哪些操作了。
5. 常见问题与避坑指南
5.1 token 统计与 credits 换算的坑
做这个 Skill 的过程中,最容易让人困惑的是“token 和 credits 的区别”。
Codex 登录后,界面上显示的是 credits,跑任务时消耗的也是 credits。但底层模型计费的单位是 token。两者之间不是固定比例,而是按模型、按输入输出方向动态换算的。
具体来说,同样一个请求,输入 token 价格便宜,输出 token 价格贵;模型档次越高,单位 token 折算的 credits 也越多。所以你在本地用脚本统计出 100 万 token,不等于你在官网看到的 credits 消耗就是某一个固定值。
这个 Skill 的价值在于“相对趋势”,而不是“绝对账单”。你想知道今天比昨天多用多少,这个统计足够准确;但如果你想和官方案单逐一对账,那还得引入模型单价表,按会话内记录的模型名逐条计价。后者复杂不少,属于更高阶的需求,我目前也没有完全做自动对账。
5.2 登录态失效与 token exchange failed 的处理
我在测试过程中碰到过几次“报告用量时提示 token exchange failed”的情况。这个错误包含的原文比较长,但核心意思就是:登录凭证已失效,无法调用后台接口。
这类问题通常和长期不活跃、网络切换后的会话过期有关。处理方式是我踩过几次坑之后总结的(注意:这一步只是处理登录失效,不涉及任何网络环境调整):
- 先退出当前会话:
codex logout - 重新登录:
codex login - 登录完成后,重开会话,再次触发“打开每日用量看板”
如果你在用旧版本客户端,升级到新版本后再做上述操作,因为旧版本的会话存储格式与新版本不一定兼容,读取会话目录时可能显示不出数据。
5.3 模型兼容性与 Skill 失效排查
另一个容易踩的坑是:Codex 更新版本后,Skill 突然不触发了。
我遇到过 Codex 升级后,旧会话文件里的消息结构变了,usage字段位置也变了,脚本统计结果变成 0。排查思路很简单:随便选一个 JSONL 文件,用编辑器打开,搜索usage,看看它在什么位置、字段名是什么。如果发现字段名不对,改脚本里的兼容分支就行。
涉及模型本身的问题也要注意。某些新模型标识符在旧的 Codex 客户端里不被支持,会出现“model not supported”之类的报错,这时候登录态是正常的,但任务跑不起来。遇到类似问题,最直接的解法是检查版本号、升级到最新版,再重试。
注意:写 Skill 的时候一定不要指定绝对路径,比如
/home/yourname/.codex/...,换一台电脑就废了。用~和$HOME派生的相对路径才是稳妥做法。
5.4 几个提升体验的小技巧
最后补充几个实战里发现的小技巧。
- 触发词别太多。三到五个就够了,写太多反而容易误触发,比如你正常聊“这个函数内存占用怎么优化”的时候,不小心命中了“用量”两个字,Skill 就会跑起来。
- 输出格式要固定。脚本里我建议把日期格式固定成
YYYY-MM-DD,不要用“今天”“昨天”这类描述,因为模型回传展示时可能会改写,一改写统计口径就乱了。 - 定期清空会话目录不是好习惯。很多人为了“节省空间”删掉旧 JSONL,代价就是历史用量统计直接丢数据。编码会话文件通常不大,留着无妨,真正占空间的往往是日志文件。
写在最后
我做这个 Skill 的过程,最大的感触是:工具能不能用好,往往不在于功能多少,而在于你多快能看到反馈。Codex 本身是个黑盒子,你问它问题、它写代码,中间消耗了多少资源,原本很难感知。现在一句话就能看到每日用了多少 token、哪个项目烧得最多、哪天跑量异常,用量管理从“月末惊吓”变成了“日常习惯”。
后面我还打算把这个 Skill 继续往“自动告警”方向扩展,比如单日 token 超过阈值就输出一条提醒,再配合定时任务推送到聊天软件里。如果你也在用 Codex 做深度开发,建议先把这个统计能力搭起来,看完一周的数据,你会对自己每天到底写了多少行代码、烧了多少 token 有一个全新认识。