CodexBar Codex Workspaces 本地索引:项目级 Codex 用量归因的架构与实现解析
2026/9/13 20:05:29 网站建设 项目流程

CodexBar Codex Workspaces 本地索引:项目级 Codex 用量归因的架构与实现解析

【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar

Codex Workspaces 是 CodexBar 中一套完全本地运行的 Codex 用量索引能力:它把已有的本地 Codex 成本扫描(cost scan)结果按项目(workspace)、会话(session)、模型(model)和天(day)四个维度重新归因,形成可持久化、可查询的项目级用量快照。它不引入任何远程 API、不做 provider 登录鉴权、不提供计费接口,也没有对外承诺公开的 CLI JSON 契约。本文基于仓库中的设计文档 docs/codex-workspaces.md,并结合 Sources/CodexBarCore/CodexLocalProjectUsageIndexer.swift、Sources/CodexBarCore/CodexWorkspaceUsageSidecar.swift、Sources/CodexBarCore/CostUsageFetcher.swift 等源码,从数据流、持久化契约、目录完整性、成本语义到隐私边界,逐层拆解这套索引的工程实现。读完本文,你将理解 CodexBar 如何在零登录的前提下把散落在本地 JSONL rollout 与 Codex 线程目录中的使用记录,聚合成一套可回滚、可失效、可隐私脱敏的 SQLite 项目级用量索引,并了解如何通过CODEXBAR_ENABLE_WORKSPACES_MENU在 Debug 构建中预览该功能的导航雏形。

Workspaces 索引的定位与边界

从 docs/codex-workspaces.md 的定义看,Codex Workspaces 的定位非常明确:它是“对现有本地 Codex 成本扫描的归因增强层(attribution layer)”,而不是一个新的数据采集通道。其能力边界包括:

  • 归因维度:项目(projects)、会话(sessions)、模型(models)、天(days);
  • 本地优先:索引、扫描、目录读取、缓存、sidecar、分析、CSV 序列化全部在本地完成,不向任何远端上传 rollout 内容或目录记录;
  • 明确不做的事:不新增远程 API、不引入 provider 认证流程、不做计费界面、不提供公开的 CLI JSON 契约。

这一边界在源码中得到印证:整个 Workspaces 功能位于 Sources/CodexBarCore 下的CodexLocalProjectUsage*CodexWorkspaceUsageSidecar.swiftCodexThreadCatalogReader.swiftCodexLocalProjectRootResolver.swift等纯本地组件中,呈现层则通过 CostUsageFetcher.swift 暴露的四个库级 API 与上层交互。

内部导航脚手架:Debug 专属的 Workspaces 菜单

在设计文档中,Workspaces 的 UI 目前只有“内部导航脚手架”(internal navigation scaffold):Debug 构建可以通过环境变量CODEXBAR_ENABLE_WORKSPACES_MENU=1暴露一个 Codex 专属的Workspaces菜单动作,打开一个可复用的原生窗口,窗口内容目前只是No data yet占位。该导航切片不会加载索引、启动扫描、读取账户数据或持久化窗口几何信息;Release 构建即使设置了该环境变量也始终禁用此开关。

源码实现与文档完全一致:

  • 门控逻辑在 CodexWorkspacesMenuAvailability.swift:environmentKey = "CODEXBAR_ENABLE_WORKSPACES_MENU",且#if DEBUG下才检查环境变量是否为"1"#else分支直接返回false
  • 窗口呈现由 CodexWorkspacesNavigation.swift 的CodexWorkspacesPresenterCodexWorkspacesWindowController完成:默认尺寸 1380×780、最小内容尺寸 980×640、禁用 tabbing 与窗口恢复(isRestorable = false),根视图是一个ContentUnavailableView("No data yet", systemImage: "folder")
  • 菜单动作通过 StatusItemController 的openCodexWorkspaces(_:)触发,并在 MenuContent.swift 的.openCodexWorkspaces分支中被调用。

也就是说,数据驱动的检查器(data-backed inspector)与明确的用户 opt-in 仍是后续的独立工作项,当前阶段只是验证了导航路径的可用性。

数据流:扫描缓存 × 线程目录的归一化管道

文档给出的 Workspaces 数据流共五步,核心思想是CostUsageScanner为唯一权威(authoritative),配合只读的 Codex 线程目录(thread catalog)做归因补充:

  1. 将本地 rollout JSONL 扫描进 Codex v11 成本缓存(cost cache);
  2. 只读地读取目录元数据,绝不修改Codex 的 catalog;
  3. 规范化 workspace 归因,并将范围限定到选定的 Codex home;
  4. 单个 SQLite 事务中发布完整的源状态(source state)与派生快照(derived snapshot);
  5. 向展示层消费者暴露项目、会话、模型、每日、源状态、进度与 CSV 库模型。

代码层面的对应关系如下:

  • 第 1 步:CostUsageScanner.loadDailyReportCancellable(provider: .codex, ...)在 CodexLocalProjectUsageIndexer.swift 中被调用,随后读取CostUsageStoreAccess.read(...)得到CostUsageCache
  • 第 2 步:CodexThreadCatalogReader.loadResult(options:)以只读方式(sqlite3_open_v2(..., SQLITE_OPEN_READONLY, nil)PRAGMA query_only = ON)读取目录,见 CodexThreadCatalogReader.swift;
  • 第 3 步:CodexLocalProjectRootResolver.projectIdentity(for: cwd)将 cwd 解析为稳定的项目身份(id / displayName / path);
  • 第 4 步:CodexWorkspaceUsageSidecar.synchronizeSources(...)synchronize(...)分别在BEGIN IMMEDIATE ... COMMIT(失败则ROLLBACK)事务中写入,见 CodexWorkspaceUsageSidecar.swift;
  • 第 5 步:buildSnapshotFromCostCache(...)产出CodexLocalProjectUsageSnapshot,其字段包括totalprojectssessionsmodelBreakdownsdailysourceStatusmodelsAnalytics,见 CodexLocalProjectUsageModels.swift。

值得注意的工程细节:sidecar 的导入只吸收扫描器派生的增量(deltas),随后从 sidecar 的规范化行重新聚合,原始 cost cache 仍是游标(cursor)与累计 token 的权威,但不再是聚合来源——这一设计在 CodexLocalProjectUsageIndexer.swift 的注释中写得很清楚,保证了索引与扫描器的职责分离。

官方支持的内部呈现边界

文档明确了五个受支持的内部呈现接口,全部集中在 CostUsageFetcher.swift:

API语义
loadCachedCodexLocalProjectUsageSnapshot纯读缓存快照,不触发扫描(内部走CodexLocalProjectUsageIndexer.cachedSnapshot
loadCodexLocalProjectUsageSnapshot可能触发扫描重建(forceRefresh时把refreshMinIntervalSeconds置 0),返回新快照并发布
clearCachedCodexLocalProjectUsageSnapshot清除指定 scope 的缓存快照(对应 sidecar 的clear(),同时删除-wal-shm文件)
CodexLocalProjectUsageSnapshot快照数据模型(Sendable & Codable & Equatable
CodexLocalProjectUsageIndexProgress进度模型:scanningLogs/indexingProjects/saving三阶段,附带 processed/total/indexed/skipped 文件计数

其中CodexLocalProjectUsageIndexProgress.Phase的完整定义在 CodexLocalProjectUsageModels.swift。索引器在每处理 1、25 的倍数或最后一个文件时上报进度(reportProgressIfNeeded),并在扫描与聚合阶段埋点CodexModelsTelemetryIndexRefresh/SnapshotAggregation两个 signpost)。

持久化契约:v11 成本缓存与 v1 sidecar

两个文件路径

文档给出的持久化路径(macOS 下)如下:

# Codex 成本缓存(CostUsageScanner 的游标与累计 token 权威) ~/Library/Caches/CodexBar/cost-usage/codex-v11.json # Workspaces sidecar(项目级归因索引) ~/Library/Caches/CodexBar/local-usage/codex-workspaces-v1.sqlite

sidecar 的路径拼接逻辑在 CodexWorkspaceUsageSidecar.swift:cacheRoot/local-usage/codex-workspaces-v1.sqlite,其中cacheRoot默认是用户 Caches 目录,也可通过CostUsageScanner.Options.cacheRoot注入。

版本契约:schema v5 + payload v3

文档明确:sidecar 当前使用SQLite schema 版本 5快照 payload 格式版本 3,这是首个发布的 Workspaces schema。数据库如果带有任何其他PRAGMA user_version,会被判定为不兼容并直接拒绝、绝不修改。源码中schemaVersion = 5snapshotPayloadFormatVersion = 3(CodexWorkspaceUsageSidecar.swift),ensureSchema只在user_version == 0(全新库)或== 5时继续,否则抛出SidecarError.incompatibleSchema(CodexWorkspaceUsageSidecar.swift)。

schema 由六张业务表组成,从建表语句可见其设计(CodexWorkspaceUsageSidecar.swift):

  • catalog_threads:目录线程元数据(id、rollout_path、cwd、title、preview、model、reasoning_effort、时间戳、archived、source_fingerprint、last_seen_generation);
  • usage_rollouts:rollout 文件级归因(session_id、cwd、title、project_path、canonical_project_path、forked_from_id、源文件 mtime/size/parsed_bytes、producer_key、pricing_key、content_fingerprint、event_detail_complete、is_present);
  • usage_daily(rollout_path, day, model)主键的每日用量(input/cached/output tokens、cost_nanos,以及被保留但“读写双方故意忽略”的 standard/priority 列);
  • usage_events(rollout_path, event_index)主键的事件级明细(timestamp、canonical/raw model、turn_id、tokens、known_cost_nanos、unpriced_tokens、pricing_model/mode、reasoning_tokens);
  • snapshot_payloads:以(scope_signature, history_days)为主键的 BLOB 快照(payload_format_version、is_complete、updated_at_ms);
  • index_statescope_signature主键的索引状态(roots_fingerprint、catalog_fingerprint、cache_producer_key、pricing_key、cache_fingerprint、last_success_ms)。

同时为usage_dailyusage_eventsusage_rolloutscatalog_threads建立了若干索引(按 rollout/day、时间戳、模型时间戳、turn、session 等)。库以 WAL 模式打开并设置PRAGMA synchronous = NORMALbusy_timeout = 250ms

v10 → v11 缓存升级语义

文档特别强调:codex-v10.json不会原地迁移,也不被接受为 v11 的增量游标。原因是“parser 与归因语义发生了变化”,因此升级后的首次扫描会执行一次性重建,并写出独立的 v11 产物:

  • v10 文件保持原样、可恢复(untouched and recoverable);
  • v11 文件仅在扫描成功后被原子替换;后续 v11 刷新可正常使用增量游标。

这一“写新文件、保留旧文件”的策略,配合“成功后才替换”的原子发布语义,是避免升级过程中数据丢失的关键。

sidecar 回滚

文档指出发布是事务性的:一次失败的同步或快照写入会整体回滚,保留上一个完整快照可用。这正是上面BEGIN IMMEDIATE / COMMIT / ROLLBACK结构的价值——读取侧loadLatestSnapshot也只选择is_complete = 1且与当前 scope、history_days 完全匹配的行,因此读到的一定是完整发布过的快照。

目录完整性与 last-good 数据

Codex 的线程目录是只读外部状态,可能出现缺失、锁定、损坏或不兼容等情况。Workspaces 索引将目录访问区分为completemissinglockedcorruptincompatible五种状态,对应 CodexThreadCatalogReader.swift 中的CodexThreadCatalogCompleteness.completeunavailable(CodexThreadCatalogFailure)(failure 枚举含 missing / locked / corrupt / incompatible / unreadable)。打开失败时按 SQLite 错误码映射:SQLITE_BUSY/SQLITE_LOCKED→ locked,SQLITE_CORRUPT/SQLITE_NOTADB→ corrupt,其余 → unreadable。

目录完整性影响侧车更新的关键分支(见 CodexWorkspaceUsageSidecar.swift):

  • 完整读取(complete)会替换该 Codex home scope 的目录元数据,并在同步后执行pruneCatalog,清理本代(generation)中已不存在的目录线程;
  • 同一 scope 稍后的不完整读取(incomplete)会报告 partial 或 stale 的源状态,但保留 last-good 的目录归因与用量快照——pruneCatalog只在catalogIsComplete时执行,避免用不完整目录误删有效元数据;
  • 稀疏 rollout 更新(sparse updates)不会抹掉已保留的标题、workspace 路径等目录元数据——usage_rollouts的 upsert 对 session_id/cwd/title 等字段使用COALESCE(excluded.xxx, usage_rollouts.xxx),即新值优先、缺省保留旧值。

作用域标识与失效指纹

文档强调:scope 标识符和失效指纹都是哈希,原始的 Codex home 路径不会被持久化为 scope 标识。源码中:

  • stableScopeSignature(options:)CodexLocalDataScope.resolve(...).identifiertimeZone组成(CodexLocalProjectUsageIndexer.swift);
  • rootsFingerprintcodexRootsFingerprint(各 sessions 根目录的哈希字典)的排序字典;
  • cacheFingerprint是对所有缓存文件逐项做 FNV 式滚动哈希得到的十六进制串(CodexWorkspaceUsageSidecar.swift)。

失效(invalidation)是按受影响部分精准触发的:changed/deleted rollout 文件、parser 或定价变化、history-window 变化、目录变化,都只使对应受影响缓存状态失效,而不是整体重建。加载快照时会逐一校验index_state中的 roots_fingerprint、cache_producer_key、pricing_key、cache_fingerprint 与 catalog_fingerprint,任一不匹配即视为缓存未命中。

成本与展示语义

已知成本与未知成本分离

Workspaces 索引在费用处理上有一条硬性规则:未知定价永远不会变成合成的零成本声明(unknown pricing never becomes a synthetic zero-cost claim)。从readTimePricingTotals(CodexLocalProjectUsageIndexer.swift)可见,逐事件用codexResolvedCostNanos解析定价:

  • 能解析出costNanos的事件计入coveredTokenshasAuthoritativeCost
  • 解析不到定价的事件 token 归入unknownCostTokens

因此最终快照中每个项目/会话同时携带knownUSDunknownTokens(见CodexLocalCostEstimate),展示层可以如实呈现“已知费用 + 未定价 token 量”两个独立指标,而不是把未知部分当作 0 元。模型分析(models analytics)、奇偶校验(parity checks)、性能遥测与 CSV 序列化全部基于同一份持久化快照运行,保证口径一致。

展示投影是瞬态的

文档列出的三条“展示不落盘”约束,在代码中体现为快照投影(projection)发生在读取/返回阶段,而非写入阶段:

  • 隐藏估算成本不会改写 sidecar——估算只在usd(fromNanos:)之类读时转换中产生;
  • 包含/排除缓存输入不会触发重扫或改写 sidecar;
  • 隐藏个人信息hidePersonalInfo)会在快照到达展示代码之前移除持久化的 workspace 路径、工作目录、workspace 名称与会话标题,同样不改写本地 sidecar。

此外,排名、合计、图表与明细必须使用同一份投影——CodexLocalProjectUsageIndexer.projecting在源状态变化时重建快照但保留其余字段,sortProjects/sortSessions在总 token、成本、会话数、最近活动等维度上给出确定性的排序规则(CodexLocalProjectUsageIndexer.swift),避免展示层各组件因投影不一致而出现排名打架。

隐私边界:纯本地处理与运行时脱敏

这是设计文档的收尾部分,也是 Workspaces 索引的底线约束:

  • 数据不离机:scanner、catalog reader、cache、sidecar、analytics 与 CSV serializer 全部在本地运行,不上传 rollout 内容或目录记录;
  • 本地索引中确实可能包含敏感字段:本地 workspace 路径、会话元数据、token 总量、派生成本估算等;
  • 发布前必须脱敏:从共享日志与截图中移除路径、标题、会话 ID、身份信息、token、密钥、网络地址与数据库行内容。

这一点与hidePersonalInfo投影、以及PersonalInfoRedactor等仓库内既有隐私组件(Sources/CodexBar/PersonalInfoRedactor.swift)的设计意图一致:本地可完整存储与计算,但任何“对外呈现”都先经过脱敏投影。

总结:一套可增量、可回滚、可脱敏的本地用量索引

回顾 docs/codex-workspaces.md 与源码实现,CodexBar 的 Codex Workspaces 本地索引在工程上呈现出一组非常清晰的取舍:

  1. 单一权威CostUsageScanner继续负责 JSONL 解析、累计 token 增量、fork/subagent 记账、定价与增量游标,Workspaces 只在其上做归因与聚合;
  2. 只读外部依赖:Codex 线程目录以SQLITE_OPEN_READONLY + query_only打开,索引绝不修改 Codex 自己的状态库;
  3. 事务化发布:sidecar 通过 WAL +BEGIN IMMEDIATE事务同时写入源状态与派生快照,失败即回滚,读取永远拿到上一个完整快照;
  4. 精准失效:scope 与指纹全部哈希化,parser/定价/目录/文件变化只使受影响缓存失效,保留 last-good 数据;
  5. 诚实的成本口径:已知成本与未知成本分离存储与呈现,绝不把未定价 token 伪装成 0 元;
  6. 隐私默认安全:数据全程本地,任何对外呈现(日志、截图、脱敏投影)都在发布前移除个人可识别信息。

对于希望继续深入研究的读者,可以沿以下路径阅读实现:数据入口 CostUsageFetcher.swift → 索引核心 CodexLocalProjectUsageIndexer.swift → 持久化 CodexWorkspaceUsageSidecar.swift → 目录读取 CodexThreadCatalogReader.swift,再到 UI 导航 CodexWorkspacesNavigation.swift 与门控 CodexWorkspacesMenuAvailability.swift。当前阶段的导航切片只服务于内部验证,数据驱动的检查器与用户 opt-in 是明确的后续工作项——这也提醒使用者在自己的构建中不要依赖尚未开放的功能路径。

【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询