news 2026/9/13 1:36:44

CodexBar 的 Zed Provider 实现详解:从 Keychain 凭据读取到云端用量快照

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CodexBar 的 Zed Provider 实现详解:从 Keychain 凭据读取到云端用量快照

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 passwordkSecClassInternetPassword,server = service URL)。兼容旧版布局的 Generic-password 回退
AccountZed 用户 ID(字符串)
SecretAccess token(UTF-8 字节)

对应的读取实现在 ZedKeychainCredentialsReader:先按kSecClassInternetPasswordkSecAttrServer精确匹配,取kSecAttrAccount作为用户 ID、kSecValueData解码为 UTF-8 作为 token;查不到时再回退到kSecClassGenericPasswordkSecAttrService匹配。

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 返回errSecInteractionNotAllowederrSecAuthFailederrSecNoAccessForItem时,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):

  1. credentials_url非空(去除首尾空白后)→ 直接作为 Keychain server/service 标识;
  2. 否则server_url非空 → 以server_url作为标识;
  3. 两者都缺省 → 默认https://zed.dev

云端 API 地址的解析规则cloudAPIURL),这里体现了安全边界:

  • 受信服务器https://zed.devhttps://staging.zed.dev会被固定路由到https://cloud.zed.dev,且允许credentials_urlserver_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.devserver_url = https://zed.example.com(跨源覆盖)→ nil。而fetch rejects cross-origin credential override测试进一步断言:在跨源配置下,凭据不会被发送到任何服务器,请求在读取凭据之前就被拒绝。

3. 云端 API 调用与响应解析

3.1 fetch 调用链

ZedStatusProbe.fetch() 的执行顺序为:

  1. 加载ZedClientSettings(缺省时直接使用默认常量cloudAPIURL);
  2. 校验server_url的 scheme 必须为 HTTPS,且跨源覆盖不成立,否则在读取 Keychain 之前抛出invalidServerURL/untrustedServerConfiguration
  3. 通过ZedCredentialsReading协议读取凭据;读不到则抛notSignedIn
  4. 发起 GET 请求,携带AuthorizationAccept: application/json头。

构造器通过协议注入credentialsReadertransportsettingsLoader三个依赖,因此测试可以用StubCredentialsReaderProviderHTTPTransportStub完整模拟 Keychain 与网络边界。

3.2 错误分类

Probe 定义了完整的错误枚举 ZedStatusProbeError,每项都有面向用户的描述文案:

错误触发条件用户可见文案(要点)
notSupported非 macOS 平台读取 Keychain"Zed is only supported on macOS."
notSignedInKeychain 中无对应条目"Not signed in to Zed. Sign in from the Zed editor app with GitHub."
keychainUnavailableKeychain 拒绝访问/授权失败"Could not read Zed credentials from the Keychain…"
invalidServerURLserver_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 …"
unauthorized401/403"Zed credentials are invalid or expired. Sign in to Zed again."
parseFailedJSON 解析失败"Could not parse Zed account response: …"

此外,传输层的CancellationErrorURLError(.cancelled)会被原样保留为取消(测试fetch preserves transport cancellation验证了这一点),避免把正常的任务取消误报为网络错误。

3.3 响应模型与解码细节

响应根结构为 ZedAuthenticatedUserResponse,包含userplan两个字段,字段映射采用 snake_case CodingKeys:plan_v3subscription_periodstarted_at/ended_at)、usage.edit_predictionshas_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),resetsAtended_at;重置文案由formatResetDescription生成分级表达:≥24h 显示 "Cycle ends in 2d 3h",否则按小时/分钟表达,已过期显示 "Cycle ended"。
  • 逾期账单(extraRateWindows)has_overdue_invoices为 true 时追加一条id = "zed.overdue-invoices"、标题 "Billing"、百分比 100 的警告窗口,文案 "Overdue invoices"。
  • 身份快照(identity)accountEmailuser.github_loginaccountOrganizationuser.nameloginMethod显示套餐名。

套餐名归一化由 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 == 50resetDescription == "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 = falsesupportsCredits = false)。
  • 抓取计划sourceModes: [.auto, .api],流水线中唯一策略是 ZedLocalFetchStrategy(idzed.localkind = .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.swiftKeychain 读取、云端 API 调用、快照映射(本文主要依据)
Sources/CodexBarCore/Providers/Zed/ZedProviderDescriptor.swiftProvider 元数据与本地抓取策略
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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 1:27:35

点分治详解:从树的重心到路径统计的三板斧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 1:26:23

别再乱用pip了:python -m pip与pip install的区别

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华