
CodexBar Kilo 组织选择与堆叠式组织用量卡片实现指南【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar本指南围绕 CodexBar 的 Kilo 组织选择Organization Selection功能展开完整覆盖从数据模型、请求层组织作用域、组织发现、配置持久化、扇出刷新到菜单堆叠渲染、Preferences 面板 UI 的端到端实现路径并给出仓库中已落地的源码与测试证据。读者将掌握X-KILOCODE-ORGANIZATIONID请求头的注入机制、tRPC/REST 双形状组织发现解析、ProviderConfig扩展持久化、以及基于KiloScopeSnapshot的多卡片堆叠渲染方案可直接用于理解或复现该功能。一、功能背景与总体目标CodexBar 是一款在菜单栏展示 OpenAI Codex 与 Claude Code 用量统计的 macOS 应用同时支持 Kiloapp.kilo.ai等多提供商。Kilo 组织选择功能的总体目标是让用户在 Preferences → Providers → Kilo 中按需开启一个或多个 Kilo 组织开启后这些组织的用量以堆叠卡片的形式与个人账户Personal一起呈现在 Kilo 菜单中。从架构层面看这一功能由四个层次构成数据模型层新增KiloOrganization与KiloUsageScope两个核心类型位于CodexBarCore模块。请求层在KiloUsageFetcher中按作用域注入X-KILOCODE-ORGANIZATIONID请求头并新增fetchOrganizations组织发现接口。状态持久化层在ProviderConfig经KiloProviderConfig扩展中持久化已知组织列表与已启用组织 ID由KiloSettingsStore提供访问器。刷新与渲染层镜像现有tokenAccounts模式在UsageStore层按启用作用域扇出fan-out拉取用量并通过已有的堆叠快照菜单管线渲染为一张作用域一张卡片。技术栈为 Swift 6、SwiftUI、Swift TestingTest验证命令包括swift build/swift test/make check。完整设计说明见 2026-05-11-kilo-organization-selection-design.md本实施计划文档见 2026-05-11-kilo-organization-selection.md功能最终落地后的用户文档见 docs/kilo.md。二、前置准备Pre-flight实施计划要求在所有开发任务开始前完成三步预检# 1. 确认 main 分支工作树干净规格提交 c24e58a4 已合入 git status # 2. 创建功能分支 git switch -c feat/kilo-organization-selection # 3. 验证 Swift 工具链与测试基线 swift build 21 | tail -5 swift test --filter KiloUsageFetcherTests 21 | tail -10预期结果是构建成功且KiloUsageFetcherTests全部通过为后续 TDD 迭代提供干净的基线。三、Task 1-2核心数据模型3.1KiloOrganization组织数据载体KiloOrganization是组织的最小数据载体字段与角色语义完全对齐 Kilo profile 接口public struct KiloOrganization: Codable, Sendable, Equatable, Hashable, Identifiable { public let id: String public let name: String public let role: String? public init(id: String, name: String, role: String? nil) { self.id id self.name name self.role role } }该类型已在 KiloOrganization.swift 落地。其中role为可选字段对应成员在组织中的角色如owner、member接口不返回时解码为nil不影响功能。类型同时满足Codable用于 JSON 编解码、Sendable跨并发域传递、Equatable/Hashable集合与去重比较与IdentifiableSwiftUI 列表渲染约束。3.2KiloUsageScope个人与组织的统一作用域抽象KiloUsageScope是本次功能最核心的抽象它把个人账户和某个组织统一建模为一种可枚举的作用域public enum KiloUsageScope: Sendable, Hashable, Equatable { case personal case organization(id: String, name: String) public var scopeIdentifier: String { switch self { case .personal: personal case let .organization(id, _): org:\(id) } } public var organizationID: String? { switch self { case .personal: nil case let .organization(id, _): id } } public var displayName: String { switch self { case .personal: Personal case let .organization(_, name): name } } }该枚举已在 KiloUsageScope.swift 落地其三个计算属性分别服务于不同层次scopeIdentifier生成稳定唯一的标识符personal或org:org_42作为KiloScopeSnapshot.id与扇出结果去重的键。organizationID请求层据此决定是否注入X-KILOCODE-ORGANIZATIONID请求头.personal返回nil。displayName渲染层据此展示卡片标题.personal固定回退为 Personal。3.3 TDD 测试证据对应测试位于 KiloOrganizationTests.swift覆盖三类行为从 Kilo 官方 profile 负载解码含id/name/role全字段缺少role字段时解码不失败org.role nil相等性比较覆盖全部存储字段同 ID 不同角色视为不同对象。KiloUsageScopeTests则验证scopeIdentifier的稳定性personal、org:org_42前缀格式、organizationID的空值语义以及displayName的展示回退。四、Task 3请求层的组织作用域——X-KILOCODE-ORGANIZATIONID头注入4.1 重构思路原fetchUsage仅接受apiKey与environment本次重构为其增加scope: KiloUsageScope .personal参数默认值保证既有调用方零迁移并将 URL 请求构建逻辑抽离为可测试的makeRequest私有方法同时暴露_buildRequestForTesting供测试直接调用public static func fetchUsage( apiKey: String, scope: KiloUsageScope .personal, environment: [String: String] ProcessInfo.processInfo.environment) async throws - KiloUsageSnapshot { guard !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { throw KiloUsageError.missingCredentials } let baseURL KiloSettingsReader.apiURL(environment: environment) let request try self.makeRequest(baseURL: baseURL, apiKey: apiKey, scope: scope) let response: ProviderHTTPResponse do { response try await ProviderHTTPClient.shared.response(for: request) } catch { throw KiloUsageError.networkError(error.localizedDescription) } if let mapped self.statusError(for: response.statusCode) { throw mapped } guard response.statusCode 200 else { throw KiloUsageError.apiError(response.statusCode) } return try self.parseSnapshot(data: response.data) } private static func makeRequest( baseURL: URL, apiKey: String, scope: KiloUsageScope) throws - URLRequest { let batchURL try self.makeBatchURL(baseURL: baseURL) var request URLRequest(url: batchURL) request.httpMethod GET request.timeoutInterval 15 request.setValue(Bearer \(apiKey), forHTTPHeaderField: Authorization) request.setValue(application/json, forHTTPHeaderField: Accept) if let orgId scope.organizationID { request.setValue(orgId, forHTTPHeaderField: X-KILOCODE-ORGANIZATIONID) } return request }上述实现已完整落地于 KiloUsageFetcher.swift。关键点头注入条件仅当scope.organizationID非空即组织作用域时才设置X-KILOCODE-ORGANIZATIONID个人作用域请求保持与旧版本完全一致。请求特征GET Bearer鉴权 Accept: application/json超时 15 秒用量抓取沿用原有 tRPC 批量端点user.getCreditBlocks、kiloPass.getState、user.getAutoTopUpPaymentMethod三个 procedure 拼接。错误映射401/403 →.unauthorized404 →.endpointNotFound5xx →.serviceUnavailable其余非 200 →.apiError。4.2 测试验证KiloUsageFetcherTests.swift 中新增两个用例直接断言请求头行为组织作用域下X-KILOCODE-ORGANIZATIONID org_42且Authorization Bearer test-token个人作用域下X-KILOCODE-ORGANIZATIONID为nil鉴权头保持正常。这保证了头只出现在组织请求上这一关键约束不被回归破坏。五、Task 4组织发现——fetchOrganizations与双形状解析5.1 主路径与回退路径fetchOrganizations采用tRPC 主路径 REST 回退双轨策略主路径调用 tRPC procedureuser.getOrganizationsbatch1input为{0:{json:null}}URL 形如baseURL/user.getOrganizations?batch1input...。回退路径当 tRPC 端点返回 404 时自动回退到 REST 接口https://api.kilo.ai/api/profile重新拉取。public static func fetchOrganizations( apiKey: String, environment: [String: String] ProcessInfo.processInfo.environment) async throws - [KiloOrganization] { guard !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { throw KiloUsageError.missingCredentials } let baseURL KiloSettingsReader.apiURL(environment: environment) let trpcRequest try self.makeOrgListTRPCRequest(baseURL: baseURL, apiKey: apiKey) do { let response try await ProviderHTTPClient.shared.response(for: trpcRequest) if response.statusCode 404 { return try await self.fetchOrganizationsRESTFallback(apiKey: apiKey) } if let mapped self.statusError(for: response.statusCode) { throw mapped } return try self.parseOrganizations(data: response.data) } catch let error as KiloUsageError { throw error } catch { throw KiloUsageError.networkError(error.localizedDescription) } }5.2 三种响应形状的容错解析parseOrganizations对 Kilo 服务端可能返回的多种 JSON 形态做了分层容错见 KiloUsageFetcher.swifttRPC 批量数组形状[ { result: { data: { json: [orgs] } } } ]也兼容data直接为数组的变体REST profile 形状{ user: {...}, organizations: [orgs] }单 procedure tRPC 扁平形状{ result: { data: { json: { organizations: [...] } } } }。decodeOrganizations负责将原始条目规整为KiloOrganizationid缺失或为空字符串的条目被直接丢弃compactMap过滤name缺失时回退为idname与role均做首尾空白清理role为空字符串时规整为nil。对应三个测试用例分别验证tRPC 数组形状解码出 2 个组织且字段正确、REST profile 形状解码且role为nil、空组织列表返回空数组。六、Task 5-6配置持久化与设置访问器6.1ProviderConfig扩展ProviderConfig是 CodexBar 每个提供商在~/.codexbar/config.json中的配置载体。组织数据通过KiloProviderConfig扩展挂载到 Kilo 配置条目上见 KiloProviderConfig.swiftpublic var kiloKnownOrganizations: [KiloOrganization]? { get { self.extensionValue(forKey: kiloKnownOrganizations) } set { self.setExtensionValue(newValue, forKey: kiloKnownOrganizations) } } public var kiloEnabledOrganizationIDs: [String]? { get { self.extensionValue(forKey: kiloEnabledOrganizationIDs) } set { self.setExtensionValue(newValue, forKey: kiloEnabledOrganizationIDs) } }两个字段均为可选kiloKnownOrganizations缓存最近一次成功拉取的组织列表含角色信息避免每次打开偏好设置都发网络请求kiloEnabledOrganizationIDs用户已勾选的组织 ID 有序列表顺序即菜单卡片展示顺序。由于走的是扩展值存储extension value而非硬编码字段老版本配置无需迁移即可无缝升级。同步模型 SyncModels.swift 中也对应声明了kiloKnownOrganizations保证 iCloud 同步场景下组织数据可随配置一并同步。6.2SettingsStore访问器KiloSettingsStore.swift 提供了五个访问器全部通过configSnapshot.providerConfig(for: .kilo)读写kiloKnownOrganizations读写已知组织列表空数组写回时置nilkiloEnabledOrganizationIDs写入前经KiloOrgIDLinkedHashSet保序去重结构清理空白与重复 ID并通过logProviderModeChange记录审计日志setKiloKnownOrganizationsPruningEnabled刷新组织列表后调用自动裁剪已失效的启用 ID——若某个已启用组织不再出现在服务端返回列表里其启用状态会被一并清除kiloIsOrganizationEnabled(_:)查询单个组织的启用状态setKiloOrganization(_:enabled:)幂等地追加或移除某个组织的启用 ID。测试见 KiloSettingsStoreTests.swift覆盖默认空值、读写持久化以及刷新后裁剪失效启用 ID三个行为其中裁剪用例验证setKiloKnownOrganizationsPruningEnabled后kiloKnownOrganizations只剩新列表且kiloEnabledOrganizationIDs中被移除组织的 ID 同步消失。七、Task 7按作用域扇出拉取fan-out7.1 分层设计策略层保持个人作用域设计文档明确了一个关键分层决策ProviderFetchStrategy协议只返回一个UsageSnapshot因此KiloAPIFetchStrategy.fetch不做任何修改仍只抓取个人作用域。组织快照的扇出完全放在UsageStore层镜像现有refreshTokenAccounts的多账户模式。这样既复用现有刷新调度、后台刷新与错误恢复逻辑又避免侵入策略协议。7.2KiloScopeSnapshot与作用域解析UsageStoreKiloOrgRefresh.swift 定义了两个核心构件struct KiloScopeSnapshot: Identifiable, Equatable { let id: String // KiloUsageScope.scopeIdentifier let scope: KiloUsageScope let snapshot: UsageSnapshot? let errorMessage: String? let sourceLabel: String? static func (lhs: KiloScopeSnapshot, rhs: KiloScopeSnapshot) - Bool { lhs.id rhs.id lhs.snapshot?.updatedAt rhs.snapshot?.updatedAt lhs.errorMessage rhs.errorMessage lhs.sourceLabel rhs.sourceLabel } }kiloEnabledScopes负责把已启用组织 ID展开为作用域数组——个人作用域永远排在第一位随后按启用顺序追加已知组织var kiloEnabledScopes: [KiloUsageScope] { var scopes: [KiloUsageScope] [.personal] let enabled self.settings.kiloEnabledOrganizationIDs guard !enabled.isEmpty else { return scopes } let knownByID Dictionary( uniqueKeysWithValues: self.settings.kiloKnownOrganizations.map { ($0.id, $0) }) for id in enabled { if let org knownByID[id] { scopes.append(.organization(id: org.id, name: org.name)) } } return scopes } func shouldFanOutKiloScopes() - Bool { self.kiloEnabledScopes.count 1 }注意这里只展开knownByID中存在的组织——即使用户手动写入了某个未知组织 ID只要不在已知列表里就不会被拉取从数据入口上杜绝了非法作用域。7.3 并发扇出与顺序保持refreshKiloScopes是扇出的执行核心凭据解析通过KiloBearerTokenResolver.resolve按当前源模式api/cli/auto解析出统一的 bearer token 与sourceLabelapi 或 cli。解析失败时为每个作用域生成携带错误信息的快照并直接返回。并发抓取用withTaskGroup为每个作用域并发执行KiloUsageFetcher.fetchUsage(apiKey:scope:environment:)单个作用域失败不拖垮整体。身份改写成功快照通过withAccountOrganization(scope.displayName)将ProviderIdentitySnapshot.accountOrganization改写为组织显示名使卡片能区分个人与各组织见 UsageStoreKiloOrgRefresh.swift。顺序保持与代际防抖并发结果按scopeIdentifier回填到scopes顺序Personal 在前、组织按启用顺序并利用isCurrentProviderRefreshGeneration校验刷新代际防止慢响应覆盖新一次刷新的结果。7.4 与主刷新流程的衔接在 UsageStoreRefresh.swift 的refreshProvider中新增分支if provider .kilo, self.shouldFanOutKiloScopes() { await self.refreshKiloScopes(generation: generation) guard self.isCurrentProviderRefreshGeneration(provider, generation: generation) else { return nil } // 继续走常规路径抓取个人快照 // 仅显示个人时现有单卡片渲染保持不变 // kiloScopeSnapshots 存在多元素时触发堆叠渲染。 } else if provider .kilo { await MainActor.run { self.kiloScopeSnapshots [] } }扇出完成后仍继续执行常规个人快照抓取这正是个人卡片与组织卡片能同时存在的原因。同时当 Kilo 被禁用或只有单作用域时shouldFanOutKiloScopes()为假kiloScopeSnapshots会被显式清空避免陈旧的多卡片残留。UsageStore中新增Published var kiloScopeSnapshots: [KiloScopeSnapshot] []属性供 UI 层观察。八、Task 8菜单堆叠渲染Kilo 菜单行生产端位于 StatusItemControllerMenu.swift// Provider-specific by design: Kilo organization scopes render as stacked account-like cards. if context.currentProvider .kilo, self.store.kiloScopeSnapshots.count 1 { let cards self.store.kiloScopeSnapshots.compactMap { scope in self.menuCardModel( for: .kilo, snapshotOverride: scope.snapshot, errorOverride: scope.errorMessage, forceOverrideCard: scope.snapshot nil) } self.addStackedMenuCards(cards, to: menu, context: context) self.addFleetAccountMenuCards(fleetProjection.additionalAccounts, to: menu, context: context) return false }渲染策略的语义非常明确触发条件kiloScopeSnapshots.count 1即至少启用了一个组织此时菜单从一张 Kilo 卡片切换为每个作用域一张卡片。快照驱动每张卡片由作用域快照的snapshot/errorMessage驱动成功则显示用量失败则该卡片显示错误信息。复用堆叠管线addStackedMenuCards与 tokenAccounts / Codex 多账户共用同一套堆叠卡片渲染管线保证视觉与交互一致。错误隔离某个组织无权限如 CLI token 未被授权时只有该卡片显示 unauthorized 错误Personal 与其他组织卡片照常渲染。菜单跟踪StatusItemControllerMenuTracking.swift也会遍历kiloScopeSnapshots确保多卡片场景下菜单项跟踪与刷新监控正常工作。CLI 渲染CLIRendererTests、MenuCardModelTests在改动后保持通过即堆叠渲染不破坏纯文本输出路径。九、Task 9Preferences 面板——Organizations 设置区9.1 描述符类型当面板需要呈现一列可开关的条目 刷新按钮时代码库通过ProviderSettingsOrganizationsDescriptorProviderDescriptor.swift描述其结构为id/title/subtitle区块标识与文案entries: () - [Entry]动态条目列表Entry含id、title、subtitle、isEnabled、isLockedonToggle: MainActor (String, Bool) - Void开关回调条目 ID 新状态onRefresh: MainActor () async - RefreshOutcome刷新按钮回调RefreshOutcome携带success与errorMessagecanRefresh: () - Bool决定刷新按钮是否可点。9.2KiloProviderImplementation实现KiloProviderImplementation.swift 中settingsOrganizations的实现要点entries第一条固定为Personal accountisLocked: true不可关闭其后按kiloKnownOrganizations顺序列出各组织subtitle展示角色owner/memberisEnabled由kiloIsOrganizationEnabled决定onToggle拦截personal假 ID防呆其余调用setKiloOrganization(orgID, enabled:)并以.userInitiated交互上下文立即触发store.refreshProvider(.kilo, allowDisabled: true)实现勾选即刷新onRefresh先经KiloBearerTokenResolver.resolve按当前源模式解析 tokencli/auto 模式无需预填 API key再调用fetchOrganizations成功后setKiloKnownOrganizationsPruningEnabled(orgs)原子地完成更新已知列表 裁剪失效启用项canRefreshapi模式要求存在 API key配置或KILO_API_KEY环境变量cli/auto模式恒为true由 fetch 时权威解析兜底。9.3 SwiftUI 渲染PreferencesProviderDetailView.swift 沿settingsTokenAccounts的既有模式渲染该区块Toggle列表 Refresh organizations 按钮 错误信息红字展示kiloOrganizationsErrorMessage状态。开关在isLocked时禁用刷新失败时在按钮旁显示LocalizedError描述。十、Task 10-12文档、验证与合入10.1 用户文档功能文档已写入 docs/kilo.md 的 Organizations 小节核心使用路径为打开 Preferences → Providers → Kilo填入 API key点击Refresh organizations勾选希望与 Personal 并排展示的组织Personal 恒显示启用至少一个组织后菜单按每个启用作用域一张卡片渲染所有用量请求都会带上标准的X-KILOCODE-ORGANIZATIONID头CLI 源模式~/.local/share/kilo/auth.json下该头同样生效若 CLI token 无权访问所选组织仅对应卡片显示 unauthorized 错误其余卡片不受影响。10.2 仓库级验证与合入合入前需跑完整验证链swift test 21 | tail -30 # 全量单元测试 make check 21 | tail -30 # swiftformat swiftlint swift build -c release 21 | tail -10 # Release 构建确认无 debug-only 类型泄漏随后以git commit -m feat(kilo): ...分任务提交模型、作用域、请求头、组织发现、持久化、访问器、扇出、渲染、UI、文档各一个提交最终通过gh pr create向主仓库提交 PR并附带设计文档链接与手动验证清单设置 API key → 刷新组织 → 勾选组织 → 观察堆叠菜单卡片吊销组织权限 → 确认仅该作用域报错。10.3 自检清单要点实施计划的自检清单明确核对了若干关键一致性约束规格的每个章节都有对应任务实现spec §2 的双形状组织发现由 Task 4 覆盖Task 7 的扇出同时覆盖 API 与 CLI 两种源模式——策略先解析 tokenrefreshKiloScopes复用同一传输层持久化Task 56、UITask 9、菜单渲染Task 8均有着落Task 1-6 严格遵循 TDD先写失败测试再实现KiloOrganization、KiloUsageScope、KiloScopeSnapshot三个类型在所有任务中保持一致命名规格中明确排除的范围menu switcher、多 key 鉴权、widget刻意未实现。十一、从实现计划到落地代码一次完整的参照若读者希望以本功能为范本理解 CodexBar 的规格 → 实施计划 → 落地代码链路可按如下映射对照阅读实施计划任务落地文件核心产出Task 1KiloOrganization.swift组织数据模型Task 2KiloUsageScope.swift作用域枚举Task 3KiloUsageFetcher.swift组织头注入Task 4KiloUsageFetcher.swift组织发现双形状解析Task 5KiloProviderConfig.swift配置持久化Task 6KiloSettingsStore.swift设置访问器Task 7UsageStoreKiloOrgRefresh.swift作用域扇出刷新Task 8StatusItemControllerMenu.swift堆叠卡片渲染Task 9KiloProviderImplementation.swiftPreferences 组织设置区Task 10docs/kilo.md用户文档配套测试分散于 KiloOrganizationTests.swift、KiloSettingsStoreTests.swift、KiloUsageFetcherTests.swift、KiloBearerTokenResolverTests.swift 与 KiloSettingsReaderTests.swift 中是理解各层行为的直接证据。十二、小结Kilo 组织选择功能的实现展示了 CodexBar 处理多账户/多作用域用量展示的通用套路用统一的作用域枚举建模个人组织在请求层用标准请求头作用域化数据在配置层持久化已知列表与启用状态在刷新层并发扇出并按序合并在渲染层复用堆叠卡片管线。其中X-KILOCODE-ORGANIZATIONID头注入、tRPC/REST 双形状容错解析、setKiloKnownOrganizationsPruningEnabled的失效裁剪以及策略层不变、Store 层扇出的分层决策都是值得在同类多租户用量聚合功能中复用的设计。【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考