CodexBar 接入 Sakana AI:账单页 Cookie 认证、5 小时/周配额窗口解析与按量付费余额的完整指南
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
本文以 CodexBar 开源仓库中docs/sakana.md为骨架,系统讲解 Sakana AI Provider 的完整接入方案:如何通过手动 Cookie 头完成认证、如何解析控制台账单页中的 5 小时与每周配额窗口、以及如何以"尽力而为"的方式抓取按量付费(Pay-as-you-go)余额与用量。读完本文,你将掌握从配置、数据源原理、异常分类到 CLI 使用的全部细节,并可从源码与测试层面理解每个解析行为的底层实现。
Sakana AI Provider 概述
Sakana AI 是一家专注基础模型与受自然启发的 AI 研究实验室。在 CodexBar 中,Sakana AI 是一个仅通过 Web 抓取(HTML scrape)获取用量数据的 Provider:它不依赖本地 CLI 会话日志,而是直接读取用户的账单页面,把订阅用户的5 小时配额窗口与每周配额窗口展示在菜单栏中。
从 Provider 元数据看(SakanaProviderDescriptor.swift),Sakana AI 的核心特征包括:
- 认证方式:手动
Cookie:头,无自动浏览器 Cookie 导入(browserCookieOrder: nil); - 数据来源:目标页面
https://console.sakana.ai/billing,纯 HTML 抓取,无 JSON API; - Source 标签:
web(fetch 计划仅含[.auto, .web]两种 sourceMode,解析策略固定为SakanaWebFetchStrategy); - CLI 名称:
sakana,别名sakana-ai; - Widget 支持:当前不可用(
widgetSelectable: false); - 默认关闭:
defaultEnabled: false,需要在设置中显式开启。
接入配置:Cookie 头的获取与存储
图形界面配置步骤
- 登录 console.sakana.ai(Sakana AI 控制台);
- 打开浏览器开发者工具,切换到Network面板,重新加载账单页面
console.sakana.ai/billing; - 从任意一条账单页请求中复制完整的
Cookie:请求头值; - 在 CodexBar 中打开Settings → Providers → Sakana AI → Cookie header,粘贴该值。
配置项的 UI 实现在 SakanaProviderImplementation.swift:设置字段以.secure类型呈现(占位符为Cookie: ...),并附带一个"Open Sakana AI Console"链接动作,点击后直接打开控制台账单页。字段说明文案明确提示该值存储在~/.codexbar/config.json中。
配置文件落盘与权限
Cookie 头以明文存储在 CodexBar 的配置文件中。配置文件位置解析规则(按优先级):
CODEXBAR_CONFIG=/path/to/config.json(显式设置时);$XDG_CONFIG_HOME/codexbar/config.json(XDG_CONFIG_HOME为绝对路径时,相对路径被忽略);- 新安装默认
~/.config/codexbar/config.json; - 老安装回退
~/.codexbar/config.json。
无论落在哪,CodexBar 在 macOS 与 Linux 上每次写入该文件都会将其权限设置为0600,保证只有当前用户可读写。文档同时给出安全提醒:手动 Cookie 属于机密信息,务必保持配置文件私密、保持0600权限、不要提交到仓库,也不要把真实 Cookie 值或可读的 DevTools 截图贴到公开 Issue 中。
环境变量方式
除了设置界面,还可以用环境变量SAKANA_COOKIE提供原始 Cookie 头值,两种方式二选一即可,设置界面写入的值与环境变量在读取时是等效的。
读取逻辑位于 SakanaSettingsReader.swift:
- 环境变量键固定为
SAKANA_COOKIE(cookieHeaderKey); - 读取后先做清洗(
cleaned):去掉首尾空白;若被成对的双引号或单引号包裹则剥离引号;最终为空串则视为未配置; - 清洗结果再交给
CookieHeaderNormalizer.normalize统一规范化,得到最终的 Cookie 头字符串。
这套规范化逻辑同时作用于环境变量与设置值:即使你从 DevTools 复制时把Cookie:前缀也带了进来,normalizer 也会把它规整为合法的 Cookie 头。相应地,如果规范化后仍为空,会抛出missingCookie错误(详见下文"错误分类")。
数据源与抓取原理
认证与请求细节
SakanaUsageFetcher.swift 是抓取核心。主请求(订阅配额)细节如下:
- 请求方法:
GET https://console.sakana.ai/billing; - 请求头:
Accept: text/html,application/xhtml+xml,Accept-Language: en-US,en;q=0.9,Cookie: <normalized cookie>; - 默认超时:15 秒(
timeout参数默认15,实际由调用方的context.webTimeout传入); - 传输层:使用
ProviderHTTPClient.redirectGuardedSession构建的 ephemeral URLSession,httpCookieStorage = nil、httpShouldSetCookies = false,即不启用 cookie 存储、不自动携带 cookie,所有凭据都来自我们手动注入的请求头——这正是"无自动浏览器 Cookie 导入"的底层保障。
登录态判定
响应会被严格校验,防止登录重定向被误当作正常数据:
- 状态码为
401/403,或位于300..<400重定向区间 →loginRequired; - 响应最终 URL 的 scheme 不是
https或 host 不是console.sakana.ai(跨域重定向)→loginRequired; - 状态码非
200→apiError(statusCode); - 响应体为空 →
parseFailed("Billing page response was empty.")。
使用详情:配额窗口解析
主行与次行:5 小时与每周窗口
- **主行(primary)**显示5 小时配额:映射为 300 分钟会话窗口(
windowMinutes: 5 * 60),并使用账单页上展示的重置时间戳(若存在); - **次行(secondary)**显示每周配额:映射为七天窗口(
windowMinutes: 7 * 24 * 60),同样使用账单页上的重置时间戳(若存在); - 每个窗口的
usedPercent从账单页相邻的% used文本解析(92% used、32% used); - 若 5 小时与每周窗口都解析不到,整个抓取抛出
parseFailed("Usage limit windows were not found.")。
对应测试见 SakanaUsageFetcherTests.swift:billing html maps five hour and weekly windows断言主窗口usedPercent == 92、windowMinutes == 300,次窗口32%、10080分钟,且主窗口重置时间被解析为指定 UTC 时刻。
重置时间必须以 UTC 解析
重置日期统一按UTC解析,而不是设备本地时区。原因是账单页总是由服务端渲染Resets on <date>(UTC 时间),浏览器只是在 JS 水合(hydration)之后才把它客户端修正为查看者的本地时间——而 CodexBar 这个纯 HTML 抓取器从不运行 JS。若改用TimeZone.current解析,每个重置时间都会被设备 UTC 偏移量整体平移(对应历史 Issue #1826)。
实现上(parseResetDate):使用en_US_POSIXlocale + UTC 时区,日期格式固定为"MMMM d, yyyy 'at' h:mm a"(例如June 23, 2026 at 2:53 PM)。测试reset date is parsed as UTC regardless of the device's local timezone会把进程默认时区强制设为 UTC+14 来验证解析结果不变,确保TimeZone.current永远不会泄漏进解析器。
解析容错也很细致:
- 百分比必须是有穷数值且在
0...100区间内,否则抛parseFailed("Invalid 5-hour usage percentage.")等;测试out of range percentages are rejected用101% used/999% used验证; - 若窗口存在但缺少重置行,百分比仍正常映射、
resetsAt为nil(测试window without reset line still maps percent); - 若重置文本格式无法解析(如
soon-ish),只丢弃时间戳,不丢百分比、不产生 resetDescription(测试unparsed reset date does not become reset description)。
计划名称与价格
计划名称与价格标签(如Standard $20/mo)会被提取并拼接,作为loginMethod身份字段用于菜单中的计划展示。测试中对应loginMethod == "Standard $20/mo"。此外 Provider 元数据里还有sharePlanLabels映射(standard、pro、enterprise等),用于跨平台分享时的计划名归一化。
Token 成本与 Credits:明确不支持
- Token 成本跟踪(
supportsTokenCost: false):不支持,成本汇总不可用。原因:Sakana 没有可供查询的组织级用量/成本 API,只有 chat completions 调用返回的逐请求usage对象(CodexBar 从不发起这类调用),因此也没有 Claude/Codex 那样的本地日志源可扫描。设置中会显示"not supported"说明文案(noDataMessage: "Sakana AI cost summary is not supported.")。 - Credits 行(
supportsCredits: false):不展示。共享的 credits 卡片 UI 路径(MenuCardView+Costs.swift)没有 Sakana 分支,若强行开启只会渲染静态creditsHint字符串而非真实余额,所以该开关保持关闭——余额改为通过独立的"Extra usage"卡片显式呈现(见下节)。
按量付费(Pay-as-you-go)余额与用量
第二个页面请求与触发条件
Sakana 还销售预付费额度用于按量付费 API 用量(模型 ID 为fugu与fugu-ultra),与订阅配额窗口相互独立。console.sakana.ai/billing会在服务端渲染这部分数据,但它位于"Pay as you go"标签页,且只有当请求 URL 带有?tab=payAsYouGo时,该标签页的 HTML 才会出现在响应中——默认/billing响应(用于订阅配额抓取)并不包含它。
因此 CodexBar 会在订阅请求之外,用同一个 Cookie 头并发发起第二次、尽力而为的 GET到https://console.sakana.ai/billing?tab=payAsYouGo。该请求受context.includeOptionalUsage控制:当 Settings → Advanced →"Show optional credits and extra usage"关闭时,includeOptionalUsage为false,整个请求直接跳过(不发任何网络请求),而不是"发了再丢弃结果"。测试fetch skips the pay as you go request entirely when optional usage is disabled断言此时捕获到的请求总数恰好为 1(只有订阅请求)。
解析三个字段
- Credit balance(余额):从
<h2>Credit balance</h2>卡片相邻的tabular-nums金额文本解析; - Recent usage total(近期用量总额):从
Usage图表头部的Total: $…文本解析,覆盖控制台当前选定的日期范围(默认最近 30 天)。React 渲染会用<!-- -->水合边界注释把标签与金额拆开,解析器会先剥离这些注释再读值(stripHTMLComments); - Date range label(日期范围标签):取"Usage date range"选择器按钮的原始文本(如
Jun 02, 2026 - Jul 01, 2026),仅作为上下文保留——CodexBar 当前不把它解释为起止日期。
实现上,SakanaPayAsYouGoSnapshot保存creditBalance、periodUsageTotal、periodLabel三个字段,balanceDetail用UsageFormatter.usdString格式化为$12.34形式。
尽力而为:永不阻塞主结果
第二次抓取从不抛错、从不阻塞主结果:如果它失败(网络错误、非 200、来源不对、空响应体或找不到预期标记),本次刷新中 pay-as-you-go 字段就直接缺席,订阅配额窗口照常返回——这由fetchPayAsYouGo的try?+ 多重 guard 保证(SakanaUsageFetcher.swift)。注意一个语义陷阱:没有购买按量付费额度的账号仍会返回$0.00余额(该卡片总是渲染),所以字段缺席几乎总是意味着请求本身失败,而非"没有额度"。
并发与生命周期控制非常精细(源码中通过SakanaPayAsYouGoResult+BoundedTaskJoin实现):
- 可选请求与必选订阅请求并发运行,但自身有5 秒上限(
payAsYouGoJoinGrace); - 主请求开始时,可选请求只获得一个200ms 的共享收集预算(
payAsYouGoEnrichmentBudget):主请求慢时不会等待,主请求快时可以在预算内短暂收集已完成的并发结果(collectPayAsYouGo); - 主请求失败或调用方取消时,可选请求被同步取消,不会追加第二个完整请求超时,也不会比触发它的刷新活得更久。
测试覆盖了这些边界:quick pay as you go response can finish after primary within the shared budget(20ms 延迟仍能合并结果)、slow pay as you go request never delays the primary quota result(阻塞的可选请求在 500ms 内被取消)、required fetch failure cancels the concurrent pay as you go request、fetch tolerates a failing pay as you go request without failing the primary fetch(500 响应下主结果不受影响)。
菜单展示与显示开关的双层门控
- 菜单:在配额窗口旁新增一张
Extra usage卡片,显示Balance: $X.XX,可用时再显示Usage: $X.XX(带日期范围标签作为 secondaryValue)。 - 双层门控:这些值在抓取层与渲染层都被 Settings → Advanced → "Show optional credits and extra usage" 控制。关闭开关只会重建菜单而不会立即重新抓取,因此如果没有渲染层门控,之前抓到的余额会残留在缓存快照中直到下次刷新——所以实时菜单卡片模型与文本描述符都各自在设置关闭时隐藏这些值,与缓存数据无关。对应到源码:
SakanaUsageSnapshot.toUsageSnapshot()只在payAsYouGo存在时构建Extra usage详情区段(SakanaUsageFetcher.swift),而ProviderUsagePresentation.optionalDetails配置了hidesAllWithoutOptionalUsage: true(SakanaProviderDescriptor.swift)。 - 不显示在菜单栏文本:与某些仅 Credits 的 Provider 不同,Sakana 已有真实的 5 小时/每周速率窗口,而"secondary metric"偏好(原本可用来在菜单栏选择备选展示)是展示每周窗口的合法途径——若复用该偏好去展示 PAYG 余额,会悄悄替换掉选择该偏好的用户的每周百分比,因此当前余额只在菜单内展示。
CLI 使用
命令行用法如下:
codexbar usage --provider sakana codexbar usage --provider sakana-ai # alias其中sakana-ai是 CLI 配置中声明的别名(SakanaProviderDescriptor.swift)。
Cookie 通过环境变量或设置界面提供:
- 环境变量:
SAKANA_COOKIE=<cookie-header-value> codexbar usage --provider sakana - 设置界面:Settings → Providers → Sakana AI → Cookie header
注意:不存在codexbar config set命令可以设置cookieHeader,只能走上面两条路径之一。这是 Sakana 与部分可脚本化配置的 Provider 的一个显著差异。
CLI 层还提供browserSupportExemption:当 sourceMode 为.auto或.web时,只要环境中存在有效的SAKANA_COOKIE环境变量,就豁免对浏览器 Cookie 导入能力的检查——因为该 Provider 本就不依赖浏览器导入。
错误分类
| 错误 | 含义 |
|---|---|
missingCookie | 未配置Cookie:头且SAKANA_COOKIE未设置(或规范化后为空)。 |
loginRequired | 请求未授权/被禁止(401/403)、发生重定向(3xx),或最终响应落在不同源(跨域登录页)。 |
apiError(Int) | 账单页返回非200状态,且不属于登录失败类别。 |
parseFailed(String) | 账单响应为空或配额数据无法解析(如找不到用量窗口、百分比越界)。 |
测试还验证了一个安全细节:fetch does not expose error response body断言 500 响应时抛出的apiError(500)不包含响应体内容,避免把账号私有信息泄漏进错误消息。错误文案在SakanaUsageError.errorDescription中定义(如"Missing Sakana cookie header (SAKANA_COOKIE)."、"Sakana login is required."等),与表格语义一一对应。
相关实现与测试文件索引
如果你要修改或调试 Sakana AI Provider,可以从这些文件入手(仓库根目录相对路径):
- SakanaProviderDescriptor.swift — Provider 元数据、抓取计划、CLI 配置(名称/别名/浏览器豁免);
- SakanaSettingsReader.swift —
SAKANA_COOKIE环境变量键、Cookie 清洗与规范化; - SakanaUsageFetcher.swift — 账单页 HTML 抓取与配额解析器;同时定义
SakanaPayAsYouGoSnapshot与按量付费标签页的抓取/解析; - SakanaProviderImplementation.swift — 设置 UI(Cookie header 字段)、可用性检查;
- MenuCardView+Costs.swift — 实时菜单卡片的余额/用量展示区(共享 credits 卡片路径,无 Sakana 专属分支);
- MenuDescriptor.swift — 文本描述符的余额与用量行;
- SakanaUsageFetcherTests.swift — 解析器回归测试(配额窗口映射、UTC 重置时间、并发预算、取消语义、错误分类等);
- 数据源页面:
https://console.sakana.ai/billing(订阅标签页)、https://console.sakana.ai/billing?tab=payAsYouGo(按量付费标签页)。
小结
Sakana AI Provider 是 CodexBar 中一个典型的"纯 Web 抓取型"接入范例:手动 Cookie 认证、HTML 解析、UTC 重置时间、双层门控的可选抓取,以及"尽力而为"并发模型。理解它的请求生命周期(一个必选订阅请求 + 一个可选的按量付费请求)与错误语义(missingCookie/loginRequired/apiError/parseFailed),是配置、排障或二次开发的关键。所有行为都有源码与测试双重佐证,读者可按上文索引直接深入验证。
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考