Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1186,15 +1186,15 @@ extension UsageMenuCardView.Model {
{
primaryResetText = openRouterQuotaDetail
}
if input.provider == .copilot,
let detail = primary.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines),
!detail.isEmpty
if input.provider == .copilot || input.provider == .wafer,
let detail = primary.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines), !detail.isEmpty
{
primaryDetailLeft = detail
}
if input.provider == .warp || input.provider == .kilo || input.provider == .mimo || input.provider == .deepseek,
let detail = primary.resetDescription,
!detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
if input.provider == .warp || input.provider == .kilo || input.provider == .mimo || input
.provider == .deepseek,
let detail = primary.resetDescription,
!detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
primaryDetailText = detail
}
Expand Down Expand Up @@ -1265,7 +1265,7 @@ extension UsageMenuCardView.Model {
primaryPacePercent = regen.pace.pacePercent
primaryPaceOnTop = regen.pace.paceOnTop
}
let primaryStatusText = input.provider == .deepseek ? primaryDetailText : nil
let primaryStatusText = (input.provider == .deepseek) ? primaryDetailText : nil
if input.provider == .deepseek {
primaryDetailText = nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ enum ProviderImplementationRegistry {
case .groq: GroqProviderImplementation()
case .llmproxy: LLMProxyProviderImplementation()
case .deepgram: DeepgramProviderImplementation()
case .wafer: WaferProviderImplementation()
}
}

Expand Down
29 changes: 29 additions & 0 deletions Sources/CodexBar/Providers/Wafer/WaferProviderImplementation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import CodexBarCore
import CodexBarMacroSupport
import Foundation

@ProviderImplementationRegistration
struct WaferProviderImplementation: ProviderImplementation {
let id: UsageProvider = .wafer

@MainActor
func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {
ProviderPresentation { _ in "api" }
}

@MainActor
func observeSettings(_: SettingsStore) {}

@MainActor
func isAvailable(context: ProviderAvailabilityContext) -> Bool {
if WaferSettingsReader.apiKey(environment: context.environment) != nil {
return true
}
return !context.settings.tokenAccounts(for: .wafer).isEmpty
}

@MainActor
func settingsFields(context _: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[]
}
}
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ProviderIcon-wafer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/ca.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@
"menu_bar_metric_subtitle_moonshot" = "Mostra el saldo de l'API de Moonshot / Kimi a la barra de menús.";
"menu_bar_metric_subtitle_mistral" = "Mostra la despesa de l'API de Mistral del mes actual a la barra de menús.";
"menu_bar_metric_subtitle_kimik2" = "Mostra els crèdits de la clau d'API de Kimi K2 a la barra de menús.";
"menu_bar_metric_subtitle_wafer" = "Mostra l'estat de la subscripció a Wafer Pass a la barra de menús.";
"automatic" = "Automàtic";
"primary_api_key_limit" = "Principal (límit de la clau d'API)";

Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
"menu_bar_metric_title" = "Menu bar metric";
"menu_bar_metric_subtitle" = "Choose which window drives the menu bar percent.";
"menu_bar_metric_subtitle_deepseek" = "Shows the DeepSeek balance in the menu bar.";
"menu_bar_metric_subtitle_wafer" = "Shows the Wafer Pass subscription status in the menu bar.";
"menu_bar_metric_subtitle_moonshot" = "Shows the Moonshot / Kimi API balance in the menu bar.";
"menu_bar_metric_subtitle_mistral" = "Shows current-month Mistral API spend in the menu bar.";
"menu_bar_metric_subtitle_kimik2" = "Shows Kimi K2 API-key credits in the menu bar.";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@
"menu_bar_metric_subtitle_moonshot" = "Muestra el saldo de la API de Moonshot / Kimi en la barra de menús.";
"menu_bar_metric_subtitle_mistral" = "Muestra el gasto de la API de Mistral del mes actual en la barra de menús.";
"menu_bar_metric_subtitle_kimik2" = "Muestra los créditos de la clave de API de Kimi K2 en la barra de menús.";
"menu_bar_metric_subtitle_wafer" = "Muestra el estado de la suscripción a Wafer Pass en la barra de menús.";
"automatic" = "Automático";
"primary_api_key_limit" = "Principal (límite de la clave de API)";

Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
"menu_bar_metric_title" = "Métrica da barra de menus";
"menu_bar_metric_subtitle" = "Escolha qual janela define a porcentagem da barra de menus.";
"menu_bar_metric_subtitle_deepseek" = "Mostra o saldo do DeepSeek na barra de menus.";
"menu_bar_metric_subtitle_wafer" = "Mostra o status da assinatura do Wafer Pass na barra de menus.";
"menu_bar_metric_subtitle_moonshot" = "Mostra o saldo da API Moonshot / Kimi na barra de menus.";
"menu_bar_metric_subtitle_mistral" = "Mostra o gasto da API Mistral no mês atual na barra de menus.";
"menu_bar_metric_subtitle_kimik2" = "Mostra os créditos da chave de API do Kimi K2 na barra de menus.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@
"menu_bar_metric_title" = "菜单栏指标";
"menu_bar_metric_subtitle" = "选择哪个窗口驱动菜单栏百分比。";
"menu_bar_metric_subtitle_deepseek" = "在菜单栏显示 DeepSeek 余额。";
"menu_bar_metric_subtitle_wafer" = "在菜单栏显示 Wafer Pass 订阅状态。";
"menu_bar_metric_subtitle_moonshot" = "在菜单栏显示 Moonshot / Kimi API 余额。";
"menu_bar_metric_subtitle_mistral" = "在菜单栏显示 Mistral API 本月支出。";
"menu_bar_metric_subtitle_kimik2" = "在菜单栏显示 Kimi K2 API Key 额度。";
Expand Down
20 changes: 20 additions & 0 deletions Sources/CodexBar/StatusItemController+Animation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,26 @@ extension StatusItemController {
{
return balance
}
if provider == .wafer {
guard let snapshot, let primary = snapshot.primary else {
return nil
}
if primary.usedPercent >= 100.0 {
return "Expired"
}
if let desc = primary.resetDescription,
let firstPart = desc.split(separator: " ").first,
let slashIdx = firstPart.firstIndex(of: "/"),
let countStr = firstPart[..<slashIdx].trimmingCharacters(in: .whitespacesAndNewlines) as String?,
let limitStr = firstPart[firstPart.index(after: slashIdx)...]
.trimmingCharacters(in: .whitespacesAndNewlines) as String?,
let count = Int(countStr),
let limit = Int(limitStr)
{
return "\(limit - count)"
}
return "Pass"
}
if provider == .moonshot,
let balance = Self.moonshotBalanceDisplayText(snapshot: snapshot)
{
Expand Down
4 changes: 3 additions & 1 deletion Sources/CodexBar/UsageStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ extension UsageStore {
.groq: "Groq debug log not yet implemented",
.llmproxy: "LLM Proxy debug log not yet implemented",
.deepgram: "Deepgram debug log not yet implemented",
.wafer: "Wafer debug log not yet implemented",
]
let buildText = {
switch provider {
Expand Down Expand Up @@ -1073,7 +1074,8 @@ extension UsageStore {
hasTokenAccount: deepSeekHasTokenAccount)
case .gemini, .antigravity, .opencode, .opencodego, .factory, .copilot, .vertexai, .kilo, .kiro, .kimi,
.kimik2, .moonshot, .jetbrains, .perplexity, .mimo, .doubao, .abacus, .mistral, .codebuff, .crof,
.windsurf, .venice, .manus, .commandcode, .stepfun, .bedrock, .grok, .groq, .llmproxy, .deepgram:
.windsurf, .venice, .manus, .commandcode, .stepfun, .bedrock, .grok, .groq, .llmproxy, .deepgram,
.wafer:
return unimplementedDebugLogMessages[provider] ?? "Debug log not yet implemented"
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand.

enum CodexParserHash {
static let value = "e478cccb0110e8ad"
static let value = "49e4b76a0238c642"
}
1 change: 1 addition & 0 deletions Sources/CodexBarCore/Logging/LogCategories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public enum LogCategories {
public static let veniceUsage = "venice-usage"
public static let vertexAIFetcher = "vertexai-fetcher"
public static let warpUsage = "warp-usage"
public static let waferUsage = "wafer-usage"
public static let webkitTeardown = "webkit-teardown"
public static let zaiSettings = "zai-settings"
public static let zaiTokenStore = "zai-token-store"
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBarCore/Providers/ProviderDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public enum ProviderDescriptorRegistry {
.groq: GroqProviderDescriptor.descriptor,
.llmproxy: LLMProxyProviderDescriptor.descriptor,
.deepgram: DeepgramProviderDescriptor.descriptor,
.wafer: WaferProviderDescriptor.descriptor,
]
private static let bootstrap: Void = {
for provider in UsageProvider.allCases {
Expand Down
12 changes: 12 additions & 0 deletions Sources/CodexBarCore/Providers/ProviderTokenResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ public enum ProviderTokenResolver {
self.deepseekResolution(environment: environment)?.token
}

public static func waferToken(
environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
{
self.waferResolution(environment: environment)?.token
}

public static func crofToken(
environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
{
Expand Down Expand Up @@ -157,6 +163,12 @@ public enum ProviderTokenResolver {
self.resolveEnv(DeepSeekSettingsReader.apiKey(environment: environment))
}

public static func waferResolution(
environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
{
self.resolveEnv(WaferSettingsReader.apiKey(environment: environment))
}

public static func crofResolution(
environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
{
Expand Down
2 changes: 2 additions & 0 deletions Sources/CodexBarCore/Providers/Providers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public enum UsageProvider: String, CaseIterable, Sendable, Codable {
case groq
case llmproxy
case deepgram
case wafer
}

// swiftformat:enable sortDeclarations
Expand Down Expand Up @@ -99,6 +100,7 @@ public enum IconStyle: Sendable, CaseIterable {
case groq
case llmproxy
case deepgram
case wafer
case combined
}

Expand Down
70 changes: 70 additions & 0 deletions Sources/CodexBarCore/Providers/Wafer/WaferProviderDescriptor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import CodexBarMacroSupport
import Foundation

@ProviderDescriptorRegistration
@ProviderDescriptorDefinition
public enum WaferProviderDescriptor {
static func makeDescriptor() -> ProviderDescriptor {
ProviderDescriptor(
id: .wafer,
metadata: ProviderMetadata(
id: .wafer,
displayName: "Wafer",
sessionLabel: "Status",
weeklyLabel: "Status",
opusLabel: nil,
supportsOpus: false,
supportsCredits: false,
creditsHint: "",
toggleTitle: "Show Wafer status",
cliName: "wafer",
defaultEnabled: false,
isPrimaryProvider: false,
usesAccountFallback: false,
browserCookieOrder: nil,
dashboardURL: "https://wafer.ai/pass",
statusPageURL: nil,
statusLinkURL: nil),
branding: ProviderBranding(
iconStyle: .wafer,
iconResourceName: "ProviderIcon-wafer",
color: ProviderColor(red: 0.43, green: 0.16, blue: 0.85)),
tokenCost: ProviderTokenCostConfig(
supportsTokenCost: false,
noDataMessage: { "Wafer cost history is not tracked via API." }),
fetchPlan: ProviderFetchPlan(
sourceModes: [.auto, .api],
pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [WaferAPIFetchStrategy()] })),
cli: ProviderCLIConfig(
name: "wafer",
aliases: [],
versionDetector: nil))
}
}

struct WaferAPIFetchStrategy: ProviderFetchStrategy {
let id: String = "wafer.api"
let kind: ProviderFetchKind = .apiToken

func isAvailable(_ context: ProviderFetchContext) async -> Bool {
Self.resolveToken(environment: context.env) != nil
}

func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
guard let apiKey = Self.resolveToken(environment: context.env) else {
throw WaferUsageError.missingCredentials
}
let usage = try await WaferUsageFetcher.fetchUsage(apiKey: apiKey)
return self.makeResult(
usage: usage.toUsageSnapshot(),
sourceLabel: "api")
}

func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
false
}

private static func resolveToken(environment: [String: String]) -> String? {
ProviderTokenResolver.waferToken(environment: environment)
}
}
34 changes: 34 additions & 0 deletions Sources/CodexBarCore/Providers/Wafer/WaferSettingsReader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Foundation

public struct WaferSettingsReader: Sendable {
public static let apiKeyEnvironmentKey = "WAFER_API_KEY"
public static let apiKeyEnvironmentKeys = [Self.apiKeyEnvironmentKey, "WAFER_KEY"]

public static func apiKey(
environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
{
for key in self.apiKeyEnvironmentKeys {
guard let raw = environment[key]?.trimmingCharacters(in: .whitespacesAndNewlines),
!raw.isEmpty
else {
continue
}
let cleaned = Self.cleaned(raw)
if !cleaned.isEmpty {
return cleaned
}
}
return nil
}

private static func cleaned(_ raw: String) -> String {
var value = raw
if (value.hasPrefix("\"") && value.hasSuffix("\"")) ||
(value.hasPrefix("'") && value.hasSuffix("'"))
{
value.removeFirst()
value.removeLast()
}
return value.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
Loading