CodexBar 的 Zed Provider 实现详解:从 Keychain 凭据读取到云端用量快照
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
CodexBar 通过「本地探测」方式监控 Zed 编辑器的套餐状态、计费周期、Edit Prediction(编辑预测)配额与逾期账单,全程无需用户额外登录。本文以 docs/zed.md 为主线,逐层展开其数据源(Keychain + Zed 云端 API)、~/.config/zed/settings.json配置覆盖机制、快照映射逻辑与故障排查手段,并结合 ZedStatusProbe.swift 等源码,帮助你在调试 Zed 用量抓取、更新 Keychain/云端 API 处理或调整 Zed Provider 的 UI/菜单行为时快速定位实现。
1. 数据源:本地探测(Keychain + 云端 API)
Zed Provider 不维护独立的登录态,而是复用 Zed 编辑器在 GitHub 登录时写入 Keychain 的凭据,再调用 Zed 云端 API 拉取用量:
GET https://cloud.zed.dev/client/users/me Authorization: {user_id} {access_token}其中Authorization头的格式由 ZedCredentials 直接拼装:
public var authorizationHeader: String { "\(self.userID) \(self.accessToken)" }这与测试用例fetch uses authorization header from keychain credentials(断言请求头为"4242 test-token",见 ZedStatusProbeTests.swift)相互印证。
1.1 Keychain 凭据布局
| 项目 | 取值 |
|---|---|
| Service URL | 默认https://zed.dev;自定义服务器场景下取配置的 HTTPSserver_url |
| Keychain 类型 | Internet password(kSecClassInternetPassword,server = service URL)。兼容旧版布局的 Generic-password 回退 |
| Account | Zed 用户 ID(字符串) |
| Secret | Access token(UTF-8 字节) |
对应的读取实现在 ZedKeychainCredentialsReader:先按kSecClassInternetPassword以kSecAttrServer精确匹配,取kSecAttrAccount作为用户 ID、kSecValueData解码为 UTF-8 作为 token;查不到时再回退到kSecClassGenericPassword按kSecAttrService匹配。
1.2 非交互式 Keychain 读取
CodexBar 请求的是非交互式Keychain 读取。两条查询都会经过 KeychainNoUIQuery.apply:
- 设置
LAContext.interactionNotAllowed = true; - 追加显式的 UI-fail 策略(运行时解析
kSecUseAuthenticationUIFail常量值,避免直接引用已废弃 API),防止在旧版 macOS 行为下仍弹出 Allow/Deny 提示。
需要注意:已有的 Zed 条目本身可能携带访问控制列表(ACL),首次被 CodexBar 读取时 macOS 仍可能弹出 SecurityAgent 授权提示,选择Always Allow即可避免反复弹窗。当 Keychain 返回errSecInteractionNotAllowed、errSecAuthFailed或errSecNoAccessForItem时,Probe 会统一抛出keychainUnavailable("Could not read Zed credentials from the Keychain…");条目不存在(errSecItemNotFound)则返回 nil,最终表现为Not signed in to Zed。
2. 配置覆盖:~/.config/zed/settings.json
CodexBar 会读取 Zed 的用户设置文件(默认路径由 defaultSettingsURL 固定为~/.config/zed/settings.json),其中credentials_url(回退到server_url)决定读取哪一个 Keychain 条目。这一行为由 ZedClientSettings 的两个计算属性实现,并有专门测试 ZedStatusProbeTests 锁定:
Keychain Service URL 的解析规则(keychainServiceURL):
credentials_url非空(去除首尾空白后)→ 直接作为 Keychain server/service 标识;- 否则
server_url非空 → 以server_url作为标识; - 两者都缺省 → 默认
https://zed.dev。
云端 API 地址的解析规则(cloudAPIURL),这里体现了安全边界:
- 受信服务器
https://zed.dev与https://staging.zed.dev会被固定路由到https://cloud.zed.dev,且允许credentials_url与server_url不同(Zed 官方对受信服务器可能使用独立的凭据标识); - 自定义服务器必须使用 HTTPS 且 host 有效,API 地址为
server_url + /client/users/me; - 跨源覆盖被拒绝:若
server_url不是受信 Zed 域名,且credentials_url与之不一致,cloudAPIURL返回 nil,Probe 抛出untrustedServerConfiguration("Zed custom servers must use HTTPS and store credentials under the same server URL.")。这样即便有人修改 settings 文件,也无法把 Keychain 里的 token 转发到另一个 host。
这一规则在测试maps server url independently from keychain identifier中被完整覆盖:http://localhost:3000(非 HTTPS)→ nil;file:///tmp/zed(非法 scheme)→ nil;credentials_url = https://zed.dev但server_url = https://zed.example.com(跨源覆盖)→ nil。而fetch rejects cross-origin credential override测试进一步断言:在跨源配置下,凭据不会被发送到任何服务器,请求在读取凭据之前就被拒绝。
3. 云端 API 调用与响应解析
3.1 fetch 调用链
ZedStatusProbe.fetch() 的执行顺序为:
- 加载
ZedClientSettings(缺省时直接使用默认常量cloudAPIURL); - 校验
server_url的 scheme 必须为 HTTPS,且跨源覆盖不成立,否则在读取 Keychain 之前抛出invalidServerURL/untrustedServerConfiguration; - 通过
ZedCredentialsReading协议读取凭据;读不到则抛notSignedIn; - 发起 GET 请求,携带
Authorization与Accept: application/json头。
构造器通过协议注入credentialsReader、transport与settingsLoader三个依赖,因此测试可以用StubCredentialsReader与ProviderHTTPTransportStub完整模拟 Keychain 与网络边界。
3.2 错误分类
Probe 定义了完整的错误枚举 ZedStatusProbeError,每项都有面向用户的描述文案:
| 错误 | 触发条件 | 用户可见文案(要点) |
|---|---|---|
notSupported | 非 macOS 平台读取 Keychain | "Zed is only supported on macOS." |
notSignedIn | Keychain 中无对应条目 | "Not signed in to Zed. Sign in from the Zed editor app with GitHub." |
keychainUnavailable | Keychain 拒绝访问/授权失败 | "Could not read Zed credentials from the Keychain…" |
invalidServerURL | server_url非 HTTPS 或无法解析 | "Zed server URL is invalid: …" |
untrustedServerConfiguration | 自定义服务器跨源凭据覆盖 | "Zed custom servers must use HTTPS and store credentials under the same server URL." |
networkError | 传输层失败 | "Zed cloud API request failed: …" |
httpError | 非 200/401/403 状态码 | "Zed cloud API returned HTTP …" |
unauthorized | 401/403 | "Zed credentials are invalid or expired. Sign in to Zed again." |
parseFailed | JSON 解析失败 | "Could not parse Zed account response: …" |
此外,传输层的CancellationError与URLError(.cancelled)会被原样保留为取消(测试fetch preserves transport cancellation验证了这一点),避免把正常的任务取消误报为网络错误。
3.3 响应模型与解码细节
响应根结构为 ZedAuthenticatedUserResponse,包含user与plan两个字段,字段映射采用 snake_case CodingKeys:plan_v3、subscription_period(started_at/ended_at)、usage.edit_predictions、has_overdue_invoices。
两个值得注意的解码点:
- 用量上限是联合类型:ZedUsageLimit 同时接受裸整数(
50)、字符串"unlimited"以及{"limited": N}三种形态,分别映射为.limited(Int)/.unlimited;无法识别时抛出dataCorrupted。测试中对"50"与"\"unlimited\""两种 fixture 分别做了断言。 - 日期使用带自定义策略的 ISO8601 解码:parseResponse 先尝试带小数秒的
.withInternetDateTime + .withFractionalSeconds,再回退到普通 ISO8601,兼容 Zed 返回的2026-05-13T00:00:00.000Z这类时间戳。
4. 快照映射:Zed 字段 → CodexBar 显示
文档定义的映射关系如下表,由 ZedUsageSnapshot.toUsageSnapshot() 实现:
| Zed 字段 | CodexBar 显示 |
|---|---|
plan.plan_v3 | 套餐标签(Free / Pro / Trial / Student / Business) |
plan.usage.edit_predictions | 主进度条:used/limit,Pro+ 显示 "Unlimited" |
plan.subscription_period.ended_at | 计费周期重置 / 次级窗口 |
plan.has_overdue_invoices | 警告提示 + 计费窗口标记 |
各字段的落地细节:
- 主进度条(primary):
makeEditPredictionsWindow中,.unlimited映射为百分比 0 且重置文案 "Unlimited";.limited(total)时把used钳制到[0, total]后计算百分比,重置文案形如"12 / 50 predictions"(total为 0 时返回 nil,不显示主条)。 - 次级窗口(secondary):以
subscription_period的起止时间计算时间进度——billingCycleUsedPercent用「当前时间 - 周期起点」占「周期总时长」的百分比(钳制在 0~100),resetsAt取ended_at;重置文案由formatResetDescription生成分级表达:≥24h 显示 "Cycle ends in 2d 3h",否则按小时/分钟表达,已过期显示 "Cycle ended"。 - 逾期账单(extraRateWindows):
has_overdue_invoices为 true 时追加一条id = "zed.overdue-invoices"、标题 "Billing"、百分比 100 的警告窗口,文案 "Overdue invoices"。 - 身份快照(identity):
accountEmail取user.github_login,accountOrganization取user.name,loginMethod显示套餐名。
套餐名归一化由 displayPlanName 完成:zed_free→ "Zed Free"、zed_pro→ "Zed Pro"、zed_pro_trial→ "Zed Pro Trial"、zed_student→ "Zed Student"、zed_business→ "Zed Business";未知值则把下划线替换为空格并首字母大写。测试display plan names normalize zed enums与快照断言(如 free 计划 10/20 →usedPercent == 50、resetDescription == "10 / 20 predictions")验证了整条映射链。
5. Provider 注册与元数据
Zed 在应用中的注册非常轻:ZedProviderImplementation 仅声明id = .zed,全部行为由 ZedProviderDescriptor 描述:
- 显示元数据:
displayName = "Zed",sessionLabel = "Edit predictions",weeklyLabel = "Billing cycle",toggleTitle = "Show Zed usage",defaultEnabled = false(默认关闭,需在设置中开启);不支持 Opus 标签与 Credits 面板(supportsOpus = false、supportsCredits = false)。 - 抓取计划:
sourceModes: [.auto, .api],流水线中唯一策略是 ZedLocalFetchStrategy(idzed.local,kind = .localProbe),它直接调用ZedStatusProbe().fetch()并把结果标记为sourceLabel = "local";shouldFallback返回 false,即该策略失败后不再降级到其他策略。 - 成本配置:
supportsTokenCost = false,提示文案 "Zed cost summary is not supported."——Zed Provider 只呈现配额与周期,不做 token 成本折算。
6. 局限性:哪些用量不算作 "Zed"
按 Zed 官方文档(LLM Providers 与 External Agents 两篇)的口径,以下内容不计入 CodexBar 的 Zed 统计:
- BYOK 模型:走用户自带的 OpenAI、Claude、Gemini 等密钥,由对应的 Provider 追踪;
- 外部代理(Claude Agent、Codex ACP 等):费用直接结算在对应服务商名下,经由那些 Provider 追踪。
也就是说,菜单栏里的 Zed 卡片反映的是 Zed 云端订阅(edit-prediction 配额与计费周期),而非通过 Zed 发起的全部 AI 消耗。
7. 故障排查
7.1 "Not signed in to Zed"
- 在Zed 编辑器应用中完成登录(Command Palette →
client: sign in); - 确认 Keychain 中存在 server 为
https://zed.dev(或你的自定义credentials_url)的 internet-password 条目。
对应代码路径:Keychain 查询返回errSecItemNotFound→ 读取器返回 nil →fetch()抛出notSignedIn,且不会发起任何网络请求(测试fetch surfaces not signed in when keychain is empty中 stub 断言了 "不应在无凭据时调用云端 API")。
7.2 "Could not read Zed credentials from the Keychain"
- macOS 可能在用户放行 CodexBar 之前一直阻止 Keychain 访问(与其他 IDE 探测类问题同类),按提示授权即可;
- 修改过
credentials_url之后,重新在 Zed 中登录一次,让新条目落到 Keychain。
对应代码路径:查询返回errSecInteractionNotAllowed/errSecAuthFailed/errSecNoAccessForItem或任何非成功状态 → 抛出keychainUnavailable。注意 macOS 上若全局 Keychain 访问被关闭(KeychainAccessGate.isDisabled),读取器也会直接以该错误失败(测试keychain reader fails closed at the foreign item boundary验证了 fail-closed 行为)。
8. 关键文件索引
| 文件 | 职责 |
|---|---|
| Sources/CodexBarCore/Providers/Zed/ZedStatusProbe.swift | Keychain 读取、云端 API 调用、快照映射(本文主要依据) |
| Sources/CodexBarCore/Providers/Zed/ZedProviderDescriptor.swift | Provider 元数据与本地抓取策略 |
| Sources/CodexBar/Providers/Zed/ZedProviderImplementation.swift | 应用侧注册 |
| Tests/CodexBarTests/ZedStatusProbeTests.swift | 云端 API、配置路由与安全边界测试 |
| Sources/CodexBarCore/KeychainNoUIQuery.swift | 非交互式 Keychain 查询的公共工具 |
阅读上述 Probe 与测试即可完整复现本文描述的行为:配置解析(ZedClientSettings)、凭据读取(ZedKeychainCredentialsReader)、请求构造(fetchAuthenticatedUser)、响应解码(parseResponse)与显示映射(toUsageSnapshot)均按「设置 → Keychain → HTTP → 映射」的顺序串联,任何一环失败都会落到第 3.2 节的错误分类之一。
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考