CodexBar CLI 重构指南:JSON-only 错误模型、配置校验与 SettingsStore 拆分实战
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
本文围绕 CodexBar 仓库内的 CLI 重构计划文档 展开,系统讲解其核心目标——让 CLI 在
usage/cost等命令下输出纯 JSON 错误、实现逐 Provider 作用域的错误载荷、落地配置校验规则,以及如何通过SettingsStore 拆分控制文件规模。读完本文,你将掌握codexbar config validate、codexbar config dump等命令的实际用法、CodexBarConfigValidator的全部校验规则与错误码,并理解 CLI 错误上报管线的底层实现位置,可直接用于排查配置问题与二次开发。
一、重构背景与目标
CodexBar 的 CLI(入口见 Sources/CodexBarCLI/CLIEntry.swift)是一个基于 Commander 框架解析参数、按命令路径分发的 Swift 可执行程序。随着 Provider 数量增长(当前仓库Sources/CodexBarCore/Providers下已有数百个 Provider 实现文件),CLI 的错误处理、配置解析与设置存储逐渐暴露出三类问题:
- 错误输出不统一:部分路径向 stderr 写文本,部分路径混入日志,机器脚本难以稳定解析失败原因;
- 配置错误静默吞掉:无效的
source、region、apiKey字段只有在运行时才以模糊错误暴露; - 文件体积膨胀:
CLIEntry.swift、SettingsStore.swift承担过多职责,单文件行数失控。
重构计划因此设定了五个目标(详见 docs/refactor/cli.md):
- JSON-only:每个错误都以合法 JSON 输出到 stdout,不再混入 stderr 文本;
- Per-provider errors:Provider 失败产出带 Provider 作用域的错误载荷;
- Config validation:对无效字段、不支持的 source 模式、错误的 region 等给出显式告警;
- Config parity:新增 CLI 命令用于校验(可选转储)配置;
- SettingsStore split:文件控制在 500 行以内,明确划分 defaults 与 config 职责。
同时,重构保留了四条约束:Provider 顺序仍由配置providers[]数组顺序驱动;Provider 启停仍由配置enabled字段控制;Provider 密钥不做 Keychain 持久化;CLI 仍支持面向非 JSON 场景的文本输出。
二、JSON-only 错误模型:错误即数据
重构的核心约定是:usage与cost命令的 JSON 输出始终是数组,错误不是单独的行外文本,而是数组内的载荷条目——正常条目携带usage/credits等数据,失败条目携带error字段。
2.1 全局/CLI 级错误形状
对于参数错误、配置加载失败这类与具体 Provider 无关的错误,使用固定的provider: "cli"、source: "cli"标识:
{ "provider": "cli", "source": "cli", "error": { "code": 1, "message": "...", "kind": "config" } }字段语义:
| 字段 | 类型 | 说明 |
|---|---|---|
provider | string | 固定为"cli",表示 CLI 自身错误 |
source | string | 固定为"cli",与 Provider 载荷中的auto/web/api等来源区分 |
error.code | int | 退出码,与进程退出码一致(见 Sources/CodexBarCLI/CLIExitCode.swift) |
error.message | string | 人类可读的错误描述 |
error.kind | string | 错误类别:args/config/provider/runtime(见 CLIErrorReporting.swift) |
2.2 源码中的实现落点
错误上报的统一出口在 Sources/CodexBarCLI/CLIErrorReporting.swift:
makeCLIErrorProviderPayload(message:code:kind:)构造provider: "cli"的ProviderPayload,source写死为"cli";makeProviderErrorPayload(provider:account:source:status:error:kind:)构造逐 Provider 作用域的错误载荷,provider使用真实的UsageProvider,source使用实际生效的ProviderSourceMode;exit(code:message:output:kind:)是唯一退出出口:当output.usesJSONOutput为真时,先打印 JSON 载荷数组再退出;否则才回退到 stderr 文本。这正对应计划中"路由所有退出到 JSON-aware reporter"的步骤 3。
ProviderPayload的结构定义在 Sources/CodexBarCLI/CLIPayloads.swift,包含provider、account、version、source、status、usage、credits、error等可空字段,JSON 编码时未设置字段自动省略。
2.3 输出格式决策链
CLIOutputPreferences(Sources/CodexBarCLI/CLIOutputPreferences.swift)决定了"何时走 JSON":
usesJSONOutput=jsonOnly || format == .json;--json与--json-only都是 JSON 快捷键,但--format显式值优先;- 从源码注释可见,
usage --format toon是唯一的 TOON 特例:TOON 借用 JSON 获取/渲染管线,但错误/退出载荷也按 TOON 渲染,避免--format toon在早期失败时悄悄回退成 JSON。
CLIEntry.swift在解析命令前就通过CLIOutputPreferences.from(argv:)预先扫描 argv 中的--json-only/--json/--pretty/--format,这正是计划步骤 3"parse--json-onlyearly"的落地实现。
三、配置校验:CodexBarConfigValidator 全规则解析
计划步骤 1 要求新增CodexBarConfigIssue与CodexBarConfigValidator,位于CodexBarCore/Config,且单文件控制在 500 行以内。这两者已实现于 Sources/CodexBarCore/Config/CodexBarConfigValidation.swift(约 324 行)。
3.1 问题模型
public enum CodexBarConfigIssueSeverity: String, Codable, Sendable { case warning case error } public struct CodexBarConfigIssue: Codable, Sendable, Equatable { public let severity: CodexBarConfigIssueSeverity public let provider: UsageProvider? public let field: String? public let code: String public let message: String }每个问题包含五个字段:severity(warning/error)、provider(可空,全局问题为空)、field(如source、apiKey、hooks.events[0].executable)、机器可读的code、人类可读的message。
3.2 顶层校验流程
validate(_ config:)按三步执行:
- 版本检查:
config.version != CodexBarConfig.currentVersion(当前版本为 1,见 CodexBarConfig.swift)时报version_mismatch错误; - 逐 Provider 校验:遍历
config.providers,调用validateProvider; - Hooks 校验:
validateHooks检查事件规则数量上限、ID 唯一性、可执行文件绝对路径、阈值范围、超时范围与命令尺寸。
3.3 Provider 级校验规则(对应文档五条规则)
计划文档列出了五条核心规则,源码实现为更细的十二类检查:
| 计划规则 | 源码错误码 | 触发条件 | 严重级 |
|---|---|---|---|
source必须在fetchPlan.sourceModes内 | unsupported_source | 配置的source不在 Provider 描述符fetchPlan.sourceModes中 | error |
apiKey仅当支持.api | api_key_unused | 设置了apiKey但 Provider 不支持apisource | warning |
| 同上 | api_source_unsupported | source == .api但 Provider 不支持api | error |
| 同上 | api_key_missing | source == .api且需要 key,但未配置任何 API 凭据(含 tokenAccounts 兜底) | warning |
cookieSource仅当支持.web/.auto | cookie_source_unused | 设置cookieSource但 Provider 不用 web cookie | warning |
| 同上 | cookie_header_unused | 设置cookieHeader但 Provider 不用 web cookie | warning |
| 同上 | cookie_header_missing | cookieSource == .manual但cookieHeader缺失 | warning |
region仅限 zai/minimax 等 | region_unused | 设置region但 Provider 描述符credentials.usesRegion为假 | warning |
workspaceID仅限 opencode 等 | workspace_unused | 设置workspaceID但 Provider 无workspaceIDValidationOrder | warning |
tokenAccounts仅在TokenAccountSupportCatalog内 | token_accounts_unused | 设置tokenAccounts但TokenAccountSupportCatalog.support(for:)返回 nil | warning |
| (额外)未知 Provider | unsupported_provider | providers[].id无 first-party 实现 | error |
(额外)secretKey误用 | secret_key_unused | 设置secretKey但 Provider 的credentials.usesSecretKey不为 true(仅 bedrock、doubao 使用) | warning |
注意:计划文档中 "region 仅用于 zai 或 minimax" 是对"当前已知使用 region 的 Provider"的概括,源码的实际判定依据是每个 Provider 描述符的
credentials.usesRegion标志(validateRegion逻辑见 CodexBarConfigValidation.swift),因此支持 region 的 Provider 集合以注册表为准,这也是避免硬编码 Provider 名单的通用做法。
3.4 Hooks 校验规则
当配置包含hooks时(HooksConfig),以下错误码会被触发:
too_many_hook_rules:规则数超过HooksConfig.maximumRuleCount;duplicate_hook_id:规则 ID 重复;invalid_hook_executable:可执行文件路径非空绝对路径;invalid_hook_provider:规则绑定的 Provider 未识别;invalid_hook_threshold:阈值必须 > 0 且 ≤ 1;invalid_hook_timeout:超时必须在 0.1~300 秒之间;invalid_hook_command_size:ID、参数或聚合命令尺寸超限。
测试用例可见 Tests/CodexBarTests/ConfigValidationTests.swift,其中reports unsafe hook rule fields与reports hook workload limits直接断言了上述错误码集合。
四、CLI 命令:config validate 与 config dump
计划步骤 2 要求新增config validate命令,可选新增config dump。两者均已落地,命令注册在 CLIEntry.swift 的config子命令组中,分发逻辑在 CLIConfigCommand.swift。
4.1 codexbar config validate
校验当前配置文件并输出问题列表:
# 文本摘要(默认) codexbar config validate # JSON 数组输出(机器可读) codexbar config validate --json-only codexbar config validate --format json --pretty行为细节(见 CLIConfigCommand.swift):
- 加载配置后调用
CodexBarConfigValidator.validate(config); - 文本模式:无问题时打印
Config: OK;有问题时逐条打印[SEVERITY] provider (field): message,Provider 为空时显示config; - JSON 模式:直接打印
[CodexBarConfigIssue]数组(--pretty可美化); - 退出码:只要存在任一
severity == .error的问题,进程以.failure退出;仅 warning 时仍以.success退出——这使config validate可直接用于 CI 门禁。
4.2 codexbar config dump
打印归一化后的配置 JSON:
codexbar config dump # 脱敏输出 codexbar config dump --show-secrets # 原始密钥(慎用)源码中sanitizedForDump(showSecrets:)(CodexBarConfig.swift)在未指定--show-secrets时会把apiKey、secretKey、cookieHeader、pluginSecrets及tokenAccounts中的 token 替换为[REDACTED],避免明文密钥进入日志或终端。
4.3 配套的 Provider 管理命令
同一个config子命令组还包含计划之外的实用命令(同样实现在 CLIConfigCommand.swift):
codexbar config providers:列出每个 Provider 的enabled/disabled状态及是否默认启用;codexbar config enable --provider <name>/codexbar config disable --provider <name>:读写配置中的enabled字段并落盘;codexbar config set-api-key --provider <name> [--api-key <key> | --stdin] [--no-enable]:为支持 API source 的 Provider 写入密钥,支持--stdin管道输入避免密钥进 shell 历史;z.ai 团队 token 还支持--label、--usage-scope team、--organization-id、--workspace-id(仅--provider zai接受这些参数,源码见resolveConfigAPIKeyAccountOptions)。
需要注意:codexbar config set-api-key --provider codex会报错并提示改用--provider openai(见unsupportedAPIKeyErrorMessage),因为 Codex 与 OpenAI Platform 是不同 Provider 实例。
五、SettingsStore 拆分:defaults 与 config 的职责分离
计划步骤 5 要求把巨型SettingsStore.swift拆分为多个 <500 行文件。该拆分已在Sources/CodexBar/目录完成,当前布局为:
- SettingsStore.swift:核心状态定义与基础能力;
- SettingsStore+Config.swift:由配置(
CodexBarConfig)支撑的计算属性; - SettingsStore+Defaults.swift:由默认值支撑的计算属性;
- SettingsStore+ProviderDetection.swift:Provider 检测逻辑;
- 另有 SettingsStore+TokenCost.swift、SettingsStore+TokenAccounts.swift、SettingsStore+MenuPreferences.swift、SettingsStore+MenuObservation.swift、SettingsStore+Sync.swift、SettingsStore+ConfigPersistence.swift 等按职责拆分的小文件。
这种"基础类型 + 领域扩展"的组织方式,让每个扩展文件只处理一类读取路径(config-backed / defaults-backed / 检测逻辑),配合.gitignore与格式化工具更容易维持行数上限。
六、Provider toggles 清理与约束保持
计划步骤 6 要求移除未使用的ProviderToggleStore及其测试,同时保留 legacy toggles 的迁移路径。之所以要保留迁移路径,是因为旧的启用/停用开关数据需要平滑并入新的providers[].enabled模型,避免用户升级后丢失启用状态。
约束方面,enabled与顺序的语义定义在 CodexBarConfig.swift:
providers[]数组顺序即 Provider 展示与处理顺序(orderedProviders());enabledProviders(metadata:)在计算启用集合时,enabled缺省时回退到 Provider 元数据的defaultEnabled;normalized()会为配置中缺失的 first-party Provider 追加默认配置(alibaba token plan 例外地追加为chinaMainlandregion),保证"配置缺失条目不等于禁用"。
这些不变量有专门测试守护(Tests/CodexBarTests/下的 SettingsStore 相关测试),重构期间通过make test回归验证。
七、验证与回归清单
计划步骤 8 给出了完整的验证路径,结合仓库现状可归纳为:
# 单元测试与风格检查 make test swiftformat Sources Tests swiftlint --strict make check # 本地编译运行冒烟 ./Scripts/compile_and_run.sh # CLI 端到端验证 codexbar --json-only --provider codex # JSON-only 错误载荷 codexbar config validate --json-only # 校验输出 JSON 数组 codexbar config validate # 文本摘要 + 非零退出码(存在 error 时) codexbar config dump --pretty # 归一化配置转储(脱敏)关键回归点:
- CLI json-only 错误载荷(无效 source、无效 Provider 选择)——对应测试在 Tests/CodexBarTests/CLIProviderSelectionTests.swift 与 CLIOutputTests.swift;
- 配置校验(bad region / source / apiKey 字段)——对应测试在 ConfigValidationTests.swift(共 492 行,覆盖 hooks 规则、Alibaba region、API 凭据等场景);
- SettingsStore 顺序与 toggle 不变量继续通过。
八、总结
CodexBar 的 CLI 重构围绕"错误即 JSON 数据"与"配置可被机器校验"两条主线展开:ProviderPayload/CLIErrorReporting提供了统一的 JSON 错误载荷出口;CodexBarConfigValidator以CodexBarConfigIssue(severity/field/code/message)的形态覆盖了计划中的全部校验规则并扩展出 hooks 校验;config validate/config dump命令让配置问题在运行前即可被发现和排查;SettingsStore的职责拆分则保证了代码库的可维护性。如果你正在为 CLI 工具设计机器可解析的错误协议或配置校验层,本文涉及的错误码表格、校验触发条件与命令行为可直接作为参考蓝本。
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考