CodexBar ZoomMate 提供方深度解析:Cookie 换 Token、Credits 用量窗口、30 天历史与 Statuspage 组件白名单
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
CodexBar 的 ZoomMate 提供方以「凭预算上限(budget cap)计费的 Credits 配额」为核心模型,通过 Zoom 官方 Web 客户端的 cookie-to-token 引导端点自动换取短时效 bearer JWT,再调用credits/status与credits/history两个第一方 API,把今日/30 天用量、预算节奏判定(Pace)内联渲染到菜单栏卡片中。读完本篇,你将掌握 ZoomMate 在 CodexBar 中的完整接入流程(Chrome 自动导入与手动 cURL 捕获两种 Cookie 来源)、bearer 令牌缓存与失效策略、数据字段到 Credits 窗口的映射逻辑、30 天历史分页抓取与日粒度聚合算法,以及 Statuspage.io 状态页组件白名单过滤的实现细节,并能在出问题时依据错误分类表快速定位原因。
提供方模型:单一 Credits 窗口与 Zoom 品牌色
ZoomMate 不是按会话/周窗口限流的提供商,而是一个凭预算上限的 Credits 配额:没有会话窗口、没有周窗口,只有一个主 "Credits" 窗口。这一点直接体现在提供方描述符 ZoomMateProviderDescriptor.swift 的元数据中:
metadata: ProviderMetadata( id: .zoommate, displayName: "ZoomMate", sessionLabel: "Credits", weeklyLabel: "Credits", opusLabel: nil, supportsOpus: false, supportsCredits: true, creditsHint: "Shows used/remaining credits against your ZoomMate budget cap.", ... dashboardURL: "https://zoommate.zoom.us/#/?settings=credit-usage", statusPageURL: "https://www.zoomstatus.com/", statusComponentAllowlist: [ /* 见后文 "Status 页" 一节 */ ]), branding: ProviderBranding( iconStyle: .init(provider: .zoommate), iconResourceName: "ProviderIcon-zoommate", // Zoom Brand Center "Visual identity > Color": // Bloom 为主色,Dawn 与 Midnight 为辅助色 color: ProviderColor(red: 11 / 255, green: 92 / 255, blue: 255 / 255), confettiPalette: [ ProviderColor(hex: 0x0B5CFF), // Bloom(主色) ProviderColor(hex: 0xB4D0F8), // Dawn ProviderColor(hex: 0x00053D), // Midnight ]),品牌色采用 Zoom 官方核心色板:Bloom(#0B5CFF)作为主色,Dawn(#B4D0F8)与 Midnight(#00053D)为辅助色,出处为 Zoom Brand Center 的 "Visual identity — Color" 页面(源码注释中标注了检索日期 2026-07-18,页面最后修改于 2025-08-28)。同一份描述符还声明了 CLI 名称(cliName: "zoommate"/ProviderCLIConfig(name: "zoommate"))、默认关闭(defaultEnabled: false)、不进入桌面小组件(widgetSelectable: false)等关键属性。
配置方式一:Chrome 自动导入(推荐)
自动模式面向仅 Chrome的 macOS 用户,步骤只有两步:
- 在 Chrome 中登录 ZoomMate;
- 在Settings → Providers中启用ZoomMate,Cookie source保持默认的Auto。
从 Cookie 到 Bearer 的完整链路
自动路径的核心思想是「用长寿命的会话 Cookie 换取短寿命的 bearer token」。CodexBar 从 Chrome 的 Cookie 罐导入 ZoomMate/Zoom 会话 Cookie(覆盖zoommate.zoom.us、ai.zoom.us以及父域zoom.us—— Zoom 的 SSO 会话 Cookie 实际作用在父域上),然后调用 ZoomMate Web 前端自己内部使用的同一个 cookie-to-token 引导端点换取短期 bearer token,全程无需手动粘贴:
GET https://ai.zoom.us/ai-computer/api/v1/login/?continue=https://zoommate.zoom.us/这一调用在 ZoomMateUsageFetcher.swift 中实现为mintBearerToken:请求携带指定主机的 Cookie 头,固定Origin/Referer为https://zoommate.zoom.us,响应体中的data.nak就是新铸造的 bearer JWT;data.user_profile.email(可选)顺带提供账号身份。由于 bearer token 由(寿命长得多)的会话 Cookie 铸造而来,只要 Chrome 中保持登录状态就能持续工作:铸造出的 token 存放在进程生命周期的内存缓存中,跨刷新复用直到接近过期,然后从同一批 Cookie 重新铸造,不需要重新粘贴。若 Chrome 中没有会话 Cookie,CodexBar 报告noSession;若 Zoom 拒绝已有会话,则报告invalidCredentials。
自动路径刻意只尝试 Chrome(browserCookieOrder在 macOS 下为[.chrome]),以避免其他浏览器存储库带来意外的 Keychain 授权弹窗;其他浏览器只能走手动捕获。
Keychain 边界与共享 Cookie 缓存
Chrome 的 Cookie 解密密钥存放在 macOS Keychain 中,因此 CodexBar只在用户主动发起的刷新时(菜单/设置刷新,或codexbar cookie命令)读取 Chrome 的 Cookie 存储。这一约束在源码中体现为resolveRequestContext的注释:Chrome 的 Cookie 解密被BrowserCookieAccessGate限制在用户发起的上下文中。
一旦某次新鲜导入验证成功(cookie-to-token 铸造成功),验证过的按主机划分的 Cookie 头会被保存到 CodexBar 的共享 Keychain Cookie 缓存(com.steipete.codexbar.cache,account 为cookie.zoommate,与其他 Cookie 类提供商相同),并在下次重新导入 Chrome 之前优先复用:
if allowCachedCookieHeader, let cached = CookieHeaderCache.load(provider: .zoommate), let cookieHeaders = ZoomMateCookieHeaders.decodeFromStorage(cached.cookieHeader), !cookieHeaders.isEmpty { logger?("[zoommate] Using cached cookie headers from \(cached.sourceLabel)") return try await Self.requestContext( forCookieHeaders: cookieHeaders, persistingValidatedHeaderAs: nil, ...) }背景刷新与随附的codexbarCLI完全基于这些缓存头运行,从不触碰 Chrome 存储、不触发 Keychain 提示。若 Zoom 拒绝缓存的会话,CodexBar 会丢弃缓存并重试一次新鲜的 Chrome 导入——但仅在用户发起的上下文中;背景刷新则报告noSession,直到下一次用户刷新。这一「重试一次」逻辑位于统一抓取策略ZoomMateWebFetchStrategy.fetch中:
do { return try await self.fetchOnce(context, allowCachedCookieHeader: true) } catch ZoomMateUsageError.invalidCredentials where cookieSource == .auto { // 持久化的 Cookie 会话(或从它铸造的 bearer)被拒绝。 // 丢弃缓存头,针对全新浏览器导入重试一次; // 用户发起之外的上下文中导入被门控拦截,重试将直接暴露 noSession。 CookieHeaderCache.clear(provider: .zoommate) return try await self.fetchOnce(context, allowCachedCookieHeader: false) }Cookie 导入的作用域收窄
ZoomMateCookieImporter.swift 从 Chrome 读取三个域的 Cookie(cookieDomains = ["zoommate.zoom.us", "ai.zoom.us", "zoom.us"],父域用于发现父作用域的 SSO Cookie,如_zm_*、cf_clearance等),随后按 RFC 6265 域匹配规则把导入集合收窄到浏览器实际会附加给ai.zoom.us/zoommate.zoom.us的 Cookie,丢弃作用域在其他无关*.zoom.us兄弟子域上的记录:
public struct ZoomMateCookieHeaders: Codable, Equatable, Sendable { static let allowedHosts = ["ai.zoom.us", "zoommate.zoom.us"] ... }把「目的地主机」内嵌在凭据值中(按主机的头字典headersByHost),从结构上保证了主机故障切换(failover)不可能把叶子主机的 Cookie 复用到其兄弟主机上。
配置方式二:手动粘贴 cURL 捕获
将 ZoomMate 提供方设置中的Cookie source改为Manual,然后粘贴从 DevTools 捕获的完整curl命令。捕获步骤:
- 打开 ZoomMate 并登录;
- 进入 AI 积分用量页面(Settings → AI credit usage 或等效入口);
- 打开开发者工具 → Network 标签页;
- 重新加载页面,找到
credits/status请求(GET https://ai.zoom.us/ai-computer/api/v1/credits/status); - 右键 → Copy →Copy as cURL;
- 把完整
curl命令粘贴到 CodexBar 设置中的ZoomMate capture字段。
手动模式与某些其他「粘贴型」提供方的一个关键差异:ZoomMate 的转发头白名单包含Authorization—— 这正是必需的凭据(见下文 "常见错误")。ZoomMateUsageFetcher.swift 中的白名单定义:
/// 手动 `.web` cURL 捕获的转发头白名单。与 T3 Chat 不同,这里 /// 必须包含 `authorization`,因为 ZoomMate 的凭据是 bearer token 而非 cookie。 private static let forwardedManualHeaders = [ "authorization": "Authorization", "cookie": "Cookie", "user-agent": "User-Agent", "accept": "Accept", "accept-language": "Accept-Language", "sec-fetch-dest": "Sec-Fetch-Dest", "sec-fetch-mode": "Sec-Fetch-Mode", "sec-fetch-site": "Sec-Fetch-Site", ]捕获的合法性由isAllowedCaptureURL严格校验,可概括为四条规则:
Cookie与选定的浏览器头可能被转发,但Origin与Referer始终被替换为固定值https://zoommate.zoom.us(防止捕获值扩大第一方请求边界);- 仅接受
ai.zoom.us或zoommate.zoom.us上、HTTPS 的/ai-computer/api/v1/credits/status端点 —— DevTools 中请求可能显示在任一口主机上(两者当前提供同一套第一方 API);源码层面还要求 URL 无端口、无 userinfo、无 query、无 fragment; - 捕获的
Cookie头绑定到该请求所在的主机:CodexBar 先尝试捕获主机,若因非鉴权类失败回退到兄弟主机,则绝不转发原主机的 Cookie; - 解析不出非空的
Authorization头时返回noCapture。
Bearer Token 生命周期(约一小时)与内存缓存策略
ZoomMate 的 bearer token 寿命很短(通常约一小时),而会话 Cookie 的寿命长得多。因此两种模式下的行为分别是:
- 自动模式:只要保持 Chrome 登录就无需任何操作;CodexBar 从会话 Cookie 铸造 bearer token 并在内存中复用,接近过期时自动重新铸造;
- 手动模式:粘贴的 token 过期(约每小时)后需要重新捕获并粘贴新的
curl命令,表现为invalidCredentials。这是预期行为而非 Bug —— 切换到自动模式即可完全规避。
令牌缓存的安全属性由 ZoomMateBearerTokenCache.swift 实现,值得逐条理解:
actor ZoomMateBearerTokenCache { static let shared = ZoomMateBearerTokenCache() /// 在 JWT 自身 exp 之前这么多秒就刷新, /// 避免在途请求骑着一个中途过期的 token。 static let refreshSkew: TimeInterval = 60 ... }- 进程生命周期、纯内存:每次启动缓存为空,铸造出的 bearer 绝不持久化;
- 不可逆键:缓存键是源主机作用域 Cookie 头的 SHA-256 十六进制摘要(
ZoomMateBearerTokenCache.key(forCookieHeaders:)),不同浏览器会话/账号不会碰撞,且原始 Cookie 不作为键存储; - 可判定过期才缓存:仅当 JWT 能解析出
exp声明时才入缓存,且只在now < exp - 60s时发放(validEntry(forKey:now:));读不到过期时间的 token 永远不入缓存,调用方退化为「每次刷新都重新铸造」,缓存因此不可能发出一个已过期的 token; - 401/403 驱逐:下游请求被拒绝(会话在自身过期前被吊销)时,策略层调用
invalidateCachedBearerToken驱逐缓存条目,下一次刷新立即重新铸造(见ZoomMateWebFetchStrategy中fetchCreditsStatus抛出invalidCredentials时的处理)。
exp的解析在expiry(fromJWT:)中完成:对 JWT 第二段做 base64url 解码后读取 JSON 中的数值exp声明(不做签名验证——token 是自己铸造的)。
数据源:Credits Status 与 Credits History
每次刷新 CodexBar 会发出至多几个 GET 请求:
GET https://ai.zoom.us/ai-computer/api/v1/credits/status GET https://ai.zoom.us/ai-computer/api/v1/credits/history?app_id=demo_app&limit=50&page=<n>&sort_by=time&sort_order=desc&start_time=<ISO8601>&end_time=<ISO8601>主机容错规则:自动模式请求先尝试ai.zoom.us,若以非鉴权错误失败则在zoommate.zoom.us上以相同路径重试一次;手动模式从捕获命令中的主机开始,非鉴权失败时切换到另一主机(不带捕获主机的 Cookie)。源码中这一机制是withAPIHostFailover,其语义在注释中写得很明确:
对
apiHosts顺序中的每个主机执行一次 API 请求,返回首个成功结果。鉴权拒绝和解析失败立即传播——主机已经应答,换到可互换的备用主机无济于事;其他任何情况(主机不可达、非鉴权 HTTP 错误)则落到下一个主机,这样任一主机退役时提供方仍可用。
状态快照与字段映射
credits/status响应的data.credit_status对象被解码为 ZoomMateModels.swift 中的ZoomMateCreditStatus结构体(budget_cap、used_credit、remaining_credit、overage_credit、allow_overage、cycle_start_date、cycle_end_date、is_quota_available、is_unlimited,日期均为 epoch 毫秒)。映射到 CodexBar 的 Credits 窗口:
| 源字段 | CodexBar 映射 |
|---|---|
used_credit/budget_cap | 主窗口usedPercent(钳制到 0–100) |
cycle_end_date | 主窗口重置时间(epoch 毫秒) |
is_unlimited/budget_cap <= 0 | usedPercent强制为 0,重置倒计时省略 |
没有次窗口;resetDescription恒为"Credits"。映射实现位于ZoomMateUsageSnapshot.toUsageSnapshot:
let usedPercent: Double = if isUnlimited || budgetCap <= 0 { 0 } else { min(100, max(0, usedCredit / budgetCap * 100)) } let resetsAt: Date? = (isUnlimited || budgetCap <= 0) ? nil : zoomMateDate(fromMilliseconds: self.creditStatus.cycleEndDate) let primary = RateWindow( usedPercent: usedPercent, windowMinutes: nil, resetsAt: resetsAt, resetDescription: "Credits")历史分页:双重终止条件与失败降级
credits/history请求是分页的(循环page直到page * limit + records.length达到响应中扁平的data.total,或某页记录全部早于请求的start_time),覆盖最近 30 天;真实账号的历史量级不大(总共几十条记录),因此通常只需 1–2 个请求。ZoomMateCreditsHistoryFetcher.swift 中还有两个工程性护栏:
/// 确认对真实账号足够便宜:30 天历史在此尺寸下最多两页, /// 远低于任何实际限流担忧。比 Web UI 的 10 更大的 limit 减少了往返次数。 public static let defaultPageLimit = 50 /// 每次抓取的分页请求硬上限,独立于账号实际历史大小—— /// 防止意外的大(或行为异常的)账号/响应(如永远满足不了的 total) /// 变成无界抓取循环。 public static let maxPages = 20分页循环的第二个终止条件值得单独说明:total反映的是账号整个历史而非请求窗口,若存在服务端过滤怪癖,仅靠total可能导致越过窗口实际需要范围的额外分页。因此当某页记录(按time降序)全部早于startTime时直接停止。整个分页循环以「单位」参与主机故障切换,保证一个快照的所有页来自同一主机。
app_id以固定占位符demo_app发送,与 ZoomMate 自家 Web UI 一致——它不对结果集做按集成方过滤。历史抓取失败不致命:它从不阻塞主credits/status快照,只意味着本次刷新省略历史仪表盘(策略层fetchOnce中history捕获invalidCredentials与其他错误均置为nil,且前者的同时会驱逐缓存的 bearer)。
账号身份(仅自动模式)
用于铸造 bearer token 的同一登录引导响应(GET .../login/?continue=...)还携带data.user_profile对象。CodexBar 从中读取user_profile.email作为提供方的accountEmail—— 与 Codex/Claude 填充的同一身份字段 —— 使菜单卡片账号行显示当前登录者,呈现方式与这些提供方一致;解析到邮箱时loginMethod设为"Cookie"。这是纯粹附加性的富集:user_profile或email缺失/不存在永远不会使 token 铸造失败,只是身份保持未设置。手动(cURL 捕获)模式没有等效的引导调用,accountEmail在该模式下保持nil。这一行为在快照映射中直接可见:
let identity = ProviderIdentitySnapshot( providerID: .zoommate, accountEmail: accountEmail, accountOrganization: nil, loginMethod: accountEmail != nil ? "Cookie" : nil)Credits 历史与节奏(Pace):内联仪表盘
启用 ZoomMate 且历史数据可用时,菜单 Credits 卡片会在积分进度条正下方内联显示 Today/30d 仪表盘与节奏判定 —— 同时出现在 ZoomMate 自有标签页和 Overview 聚合视图中,与 Claude/Codex 的内联仪表盘一致。它包含三部分:
- 两个 KPI 磁贴,复用 Claude/Codex 渲染内联仪表盘所用的同一个
InlineUsageDashboardContent组件(InlineUsageDashboardContent.swift):Today(强调显示 —— 当前日历日在credits/history中累加的cost,今天尚无记录则为 0)与30d credits(整个 30 天窗口的合计)。这对应 Codex 的 "Today" / "30d cost" 磁贴对和 Claude 的 "Today" / "30d spend" 磁贴,只是把 "$"/成本单位换成了 "credits"(ZoomMate 没有美元成本概念); - 磁贴下方的一排迷你用量条,每个有 ZoomMate 用量的日历日一根(
credits/history的按事件台账按日汇总),上限为最近 30 天,渲染为 ZoomMate 品牌色#0B5CFF—— 与 Codex/Claude 自身的条密度及按提供方着色完全一致; - 节奏行("Pace: on track" / "Pace: N% ahead of budget" / "Pace: N% behind budget"),渲染为迷你条下方的普通内联仪表盘详情行。它由
credits/status的budget_cap、累计used_credit以及当前计费周期的cycle_start_date/cycle_end_date计算 —— 把实际用量与周期线性已过时间占比对比。它复用UsagePace既有的阶段阈值(on track / slightly ahead / ahead / far ahead / slightly behind / behind / far behind),而非 ZoomMate 专属刻度,措辞因此与其他 CodexBar 节奏指示器一致。
源码中的节奏计算值得注意一个细节:ZoomMate 计费周期长度任意(不是固定周节律),所以把windowMinutes设为实际周期分钟数,workDays: nil使UsagePace.weekly()的工作日感知分支永不触发,退化为纯粹的线性周期占比对比:
public func pacingVerdict(now: Date = Date()) -> UsagePace? { guard let budgetCap, budgetCap > 0, self.isUnlimited != true, let cycleStartMillis = self.cycleStartDate, let cycleEndMillis = self.cycleEndDate else { return nil } ... let window = RateWindow( usedPercent: usedPercent, windowMinutes: cycleMinutes, resetsAt: cycleEnd, resetDescription: "Credits") return UsagePace.weekly(window: window, now: now, workDays: nil) }聚合规则(dailyBreakdown(),见 ZoomMateModels.swift):
is_deleted历史记录从 KPI 磁贴和迷你条中都排除;仍在运行的会话(is_running: true)计入,因为其cost反映迄今消耗;- 30 天窗口在抓取时(请求的
start_time)和展示时(dailyBreakdown()无论如何都过滤到最近 30 个日历日)双重强制执行,图表的日历跨度因此有保证; - 无法解析
time或cost为负的记录被防御性跳过; - 节奏行只需要每次必抓的
credits/status快照,因此即使某次刷新credits/history失败或无数据也能出现; - 内联区块以「有非空日粒度分解或可计算的节奏判定」为门控 —— 空/失败的历史抓取会静默省略整个区块,而不是显示一个空仪表盘。
Status 页:Statuspage.io 共享通道 + 组件白名单
ZoomMate 的描述符指向 Zoom 的公开状态页(zoomstatus.com),这是一个 Atlassian Statuspage.io 站点 —— 与 Claude 状态页所用平台相同。这意味着整体状态行及其 "Updated …" 副标题通过 CodexBar 既有的共享 Statuspage.io 抓取/解析通道工作,没有ZoomMate 专属的抓取代码。
Zoom 的状态页列出 300+ 组件,被大量与 ZoomMate 无关的服务(Zoom Phone、Contact Center、CX 各自按区域重复)所主导。全部展示在 ZoomMate 的状态下钻中会是噪音,因此组件子菜单被过滤到一个命名白名单:
- Zoom Meetings
- ZoomMate
- My Notes
- Zoom Workflows
- Zoom Developer Platform
- Zoom Support
- Zoom Website
白名单在描述符元数据statusComponentAllowlist中声明(见上文描述符代码),过滤发生在 StatusItemController+Menu.swift 的statusComponentsSubmenuProviders与描述符驱动的filterStatusComponents。其容错语义:
- 对实时 API 返回的内容做大小写敏感的组件/分组名精确匹配,容忍 Zoom 侧重命名或移除任意子集:缺失的名字被静默省略,绝非错误;
- 若白名单名字一个都不在场,子菜单回退到其他提供方已有的 "components not loaded" 空状态(没有 ZoomMate 专属空状态 UI);
- "Zoom Meetings" 和 "Zoom Workflows" 在 Zoom 的数据中本身就是分组(各有子组件);白名单中的分组展开时显示其完整既有子列表,与其他提供方一致;
- 其余所有提供方都没有描述符白名单,保持展示其数据源返回的全部组件,不受影响。
CLI 使用
codexbar usage --provider zoommateCLI 复用先前一次验证成功刷新所缓存的主机作用域 Cookie 头;它自身不读取 Chrome 的 Cookie 存储。若尚不存在缓存会话(noSession),从应用刷新一次,或在终端播种缓存:
codexbar cookie --provider zoommate(可加--allow-keychain-prompt确认 Chrome Cookie 解密可能触发提示。)
注意能力边界:ZoomMate 不提供 token 成本数据(描述符中supportsTokenCost: false),且暂不支持 CodexBar 桌面小组件 —— 这包括上文提到的 credits 历史仪表盘与状态页,二者目前均为应用菜单专属。
常见错误速查
| 错误 | 原因 | 修复 |
|---|---|---|
noCapture | 选择了手动模式但捕获为空、域名不符、或缺少可解析的Authorization头 | 从ai.zoom.us或zoommate.zoom.us重新粘贴 HTTPScredits/status请求的 cURL 捕获 |
noSession | 自动模式下既无缓存会话,也没有可读取的 ZoomMate/Zoom 会话 Cookie(背景刷新与 CLI 从不直接读 Chrome) | 在 Chrome 登录 ZoomMate 并从应用刷新一次(或codexbar cookie --provider zoommate),或切换到手动粘贴捕获 |
invalidCredentials | HTTP 401/403 —— token 过期(约一小时)或被吊销 | 重新登录(自动)或重新粘贴新捕获(手动) |
apiError | 其他任何非 200 HTTP 状态 | 检查 ZoomMate 状态页;稍后重试 |
parseFailed | HTTP 200 响应体不含预期的credit_status形状 | 附带脱敏响应样本提交 CodexBar issue |
五种错误在 ZoomMateModels.swift 中统一建模为ZoomMateUsageError枚举,每个 case 都携带用户可读的errorDescription。
隐私与请求边界小结
- 铸造出的 bearer token 只存在于进程生命周期的内存缓存中,绝不持久化;持久化到共享 Keychain Cookie 缓存的是验证过的主机作用域 Cookie 头(仅这些头,绝不含 bearer),生命周期与其他 Cookie 类提供方(Claude Web、Perplexity、OpenCode 等)一致;
- ZoomMate 日志刻意省略 Cookie、bearer token、
nak值与原始响应体; - 所有鉴权与积分请求都使用两个第一方 API 主机上的固定 HTTPS URL,每个请求只携带作用域到其目标主机的 Cookie;
Origin/Referer/continue固定为 ZoomMate Web 客户端值;父域zoom.us的作用域仅用于发现父作用域 SSO Cookie,不授权对任意 Zoom 子域的请求; - ZoomMate 目前没有公开文档的公共 API,因此该提供方依赖产品自身的第一方 Web 客户端端点;这种「cookie 换 bearer」形状沿用了 CodexBar 在 Factory 提供方 WorkOS Cookie 交换中的既有先例。
关键文件索引
- ZoomMateProviderDescriptor.swift — 提供方元数据(含
statusPageURL与状态组件白名单)、品牌色,以及统一抓取策略ZoomMateWebFetchStrategy(同时调用credits/status与credits/history,处理缓存失效与一次重试) - ZoomMateUsageFetcher.swift — credits/status 请求、cURL 捕获解析、cookie-to-token 铸造、主机故障切换、JWT
exp解析 - ZoomMateCreditsHistoryFetcher.swift — credits/history 请求(
limit=50、maxPages=20、日期边界停止分页)与ZoomMateCreditsHistorySnapshot模型 - ZoomMateModels.swift — 响应解码、错误分类(
ZoomMateUsageError)、窗口映射(toUsageSnapshot)、日粒度聚合(dailyBreakdown())、今日合计(todayCreditsUsed(now:calendar:))、节奏判定(pacingVerdict) - ZoomMateCookieImporter.swift — Chrome Cookie 罐导入(仅 macOS)与 RFC 6265 域匹配收窄
- ZoomMateBearerTokenCache.swift — 进程生命周期内存 bearer 缓存(SHA-256 键、60s 刷新偏斜、401/403 驱逐)
- ZoomMateProviderSettings.swift — Cookie 来源与手动捕获的持久化模型
- InlineUsageDashboardContent.swift — 与 Claude/Codex/OpenRouter 等共享的 Today/30d KPI 磁贴 + 迷你条视图;ZoomMate 经由同一组件渲染
- StatusItemController+Menu.swift —
statusComponentsSubmenuProviders与描述符驱动的filterStatusComponents - 测试:ZoomMateUsageFetcherTests.swift、ZoomMateCreditsHistoryFetcherTests.swift、ZoomMateCookieCacheTests.swift
最后说明适用前提:自动导入路径仅支持 macOS + Chrome(其他平台下resolveRequestContext直接抛noSession,isAvailable在非 macOS 下返回 false),且依赖 ZoomMate 第一方 Web 端点的行为保持稳定;任一 API 主机未来退役时,withAPIHostFailover的双主机回退是当前设计给出的缓冲。
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考