Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ fn cookie_source_provider(provider_id: &str) -> Option<codexbar::core::ProviderI
"ollama" => ProviderId::Ollama,
"mistral" => ProviderId::Mistral,
"qoder" => ProviderId::Qoder,
"codebuddy" => ProviderId::CodeBuddy,
"sakana" => ProviderId::Sakana,
"notion" => ProviderId::Notion,
_ => return None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export const PROVIDER_ICON_REGISTRY: Record<string, ProviderIcon> = {
crof: { id: "crof", brandColor: "#7c3aed", fallbackLetter: "C", svgPath: RAW.crof },
crossmodel: { id: "crossmodel", brandColor: "#c084fc", fallbackLetter: "X", svgPath: RAW.crossmodel },
qoder: { id: "qoder", brandColor: "#2563eb", fallbackLetter: "Q", svgPath: RAW.qoder },
codebuddy: { id: "codebuddy", brandColor: "#0052d9", fallbackLetter: "C" },
sakana: { id: "sakana", brandColor: "#0ea5e9", fallbackLetter: "S", svgPath: RAW.sakana },
stepfun: { id: "stepfun", brandColor: "#999999", fallbackLetter: "S", svgPath: RAW.stepfun },
sub2api: { id: "sub2api", brandColor: "#2dc6d8", fallbackLetter: "S", svgPath: RAW.sub2api },
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop-tauri/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -4228,19 +4228,28 @@ html:has(.menu-surface--tray) {
align-items: baseline;
justify-content: space-between;
gap: 8px;
min-width: 0; /* allow children to shrink/ellipsis inside flex */
}

.menu-metric__pct {
/* upstream: .font(.footnote) ≈ 11px primary */
font-size: 11px;
color: var(--text-primary);
font-variant-numeric: tabular-nums;
flex: 0 0 auto;
}

.menu-metric__reset {
font-size: 11px;
color: var(--text-secondary);
white-space: nowrap;
/* Long provider descriptions (credits remaining, etc.) used to overflow
the tray card because nowrap had no max-width/ellipsis. */
min-width: 0;
max-width: 62%;
overflow: hidden;
text-overflow: ellipsis;
text-align: right;
}

.menu-metric__exhausted {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop-tauri/src/surfaces/TrayPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const HAS_DASHBOARD = new Set([
"doubao", "elevenlabs", "factory", "gemini", "grok", "groq",
"infini", "jetbrains", "kilo", "kimi", "kimik2", "kiro", "manus",
"mimo", "minimax", "mistral", "nanogpt", "notion", "ollama", "openaiapi",
"opencode", "opencodego", "openrouter", "perplexity", "qoder", "sakana", "stepfun",
"opencode", "opencodego", "openrouter", "perplexity", "qoder", "codebuddy", "sakana", "stepfun",
"t3chat", "venice", "vertexai", "warp", "windsurf",
"xai", "zai",
]);
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/test/providerCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const TEST_PROVIDER_CATALOG: Array<[string, string]> = [
["zed", "Zed"],
["crossmodel", "CrossModel (removed)"],
["qoder", "Qoder"],
["codebuddy", "CodeBuddy"],
["sakana", "Sakana AI"],
["sub2api", "sub2api"],
["qwencloud", "Qwen Cloud"],
Expand Down
11 changes: 10 additions & 1 deletion rust/src/core/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub enum ProviderId {
Zed,
CrossModel,
Qoder,
CodeBuddy,
Sakana,
Sub2Api,
Wayfinder,
Expand Down Expand Up @@ -142,6 +143,7 @@ impl ProviderId {
ProviderId::Zed,
ProviderId::CrossModel,
ProviderId::Qoder,
ProviderId::CodeBuddy,
ProviderId::Sakana,
ProviderId::Sub2Api,
ProviderId::Wayfinder,
Expand Down Expand Up @@ -216,6 +218,7 @@ impl ProviderId {
ProviderId::Zed => "zed",
ProviderId::CrossModel => "crossmodel",
ProviderId::Qoder => "qoder",
ProviderId::CodeBuddy => "codebuddy",
ProviderId::Sakana => "sakana",
ProviderId::Sub2Api => "sub2api",
ProviderId::Wayfinder => "wayfinder",
Expand Down Expand Up @@ -292,6 +295,7 @@ impl ProviderId {
// Soft-removed (upstream #2254); still resolvable via CLI for legacy configs.
ProviderId::CrossModel => "CrossModel (removed)",
ProviderId::Qoder => "Qoder",
ProviderId::CodeBuddy => "CodeBuddy",
ProviderId::Sakana => "Sakana AI",
ProviderId::Sub2Api => "sub2api",
ProviderId::Wayfinder => "Wayfinder",
Expand Down Expand Up @@ -339,6 +343,7 @@ impl ProviderId {
ProviderId::CommandCode => Some("commandcode.ai"),
ProviderId::Grok => Some("grok.com"),
ProviderId::Qoder => Some("qoder.com"),
ProviderId::CodeBuddy => Some("codebuddy.cn"),
ProviderId::Sakana => Some("console.sakana.ai"),
ProviderId::LongCat => Some("longcat.chat"),
// Token-based providers (don't use cookies)
Expand Down Expand Up @@ -457,6 +462,9 @@ impl ProviderId {
Some(ProviderId::CrossModel)
}
"qoder" => Some(ProviderId::Qoder),
"codebuddy" | "code-buddy" | "codebuddy-cn" | "codebuddycn" | "腾讯codebuddy" => {
Some(ProviderId::CodeBuddy)
}
"sakana" | "sakana-ai" | "sakana ai" => Some(ProviderId::Sakana),
"sub2api" | "sub-2-api" | "sub 2 api" => Some(ProviderId::Sub2Api),
"wayfinder" => Some(ProviderId::Wayfinder),
Expand Down Expand Up @@ -732,7 +740,7 @@ mod tests {
#[test]
fn test_provider_id_all() {
let all = ProviderId::all();
assert_eq!(all.len(), 68);
assert_eq!(all.len(), 69);
assert!(all.contains(&ProviderId::Claude));
assert!(all.contains(&ProviderId::Codex));
assert!(all.contains(&ProviderId::Kimi));
Expand Down Expand Up @@ -770,6 +778,7 @@ mod tests {
assert!(all.contains(&ProviderId::Zed));
assert!(all.contains(&ProviderId::CrossModel));
assert!(all.contains(&ProviderId::Qoder));
assert!(all.contains(&ProviderId::CodeBuddy));
assert!(all.contains(&ProviderId::Sakana));
assert!(all.contains(&ProviderId::Sub2Api));
assert!(all.contains(&ProviderId::Wayfinder));
Expand Down
24 changes: 13 additions & 11 deletions rust/src/core/provider_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ use super::{Provider, ProviderId};
use crate::providers::{
AbacusProvider, AiAndProvider, AlibabaProvider, AlibabaTokenPlanProvider, AmpProvider,
AntigravityProvider, AugmentProvider, AzureOpenAIProvider, BedrockProvider, ChutesProvider,
ClaudeProvider, ClinePassProvider, CodebuffProvider, CodexProvider, CommandCodeProvider,
CopilotProvider, CrofProvider, CrossModelProvider, CursorProvider, DeepInfraProvider,
DeepSeekProvider, DeepgramProvider, DevinProvider, DoubaoProvider, ElevenLabsProvider,
FactoryProvider, GeminiProvider, GrokProvider, GroqProvider, InfiniProvider, JetBrainsProvider,
KiloProvider, KimiK2Provider, KimiProvider, KiroProvider, LLMProxyProvider, LiteLLMProvider,
LongCatProvider, ManusProvider, MiMoProvider, MiniMaxProvider, MistralProvider,
NanoGPTProvider, NeuralwattProvider, NotionProvider, OllamaProvider, OpenAIApiProvider,
OpenCodeGoProvider, OpenCodeProvider, OpenRouterProvider, PerplexityProvider, PoeProvider,
QoderProvider, QwenCloudProvider, SakanaProvider, StepFunProvider, Sub2ApiProvider,
T3ChatProvider, VeniceProvider, VertexAIProvider, WarpProvider, WayfinderProvider,
WindsurfProvider, XaiProvider, ZaiProvider, ZedProvider, ZenMuxProvider, ZoomMateProvider,
ClaudeProvider, ClinePassProvider, CodeBuddyProvider, CodebuffProvider, CodexProvider,
CommandCodeProvider, CopilotProvider, CrofProvider, CrossModelProvider, CursorProvider,
DeepInfraProvider, DeepSeekProvider, DeepgramProvider, DevinProvider, DoubaoProvider,
ElevenLabsProvider, FactoryProvider, GeminiProvider, GrokProvider, GroqProvider,
InfiniProvider, JetBrainsProvider, KiloProvider, KimiK2Provider, KimiProvider, KiroProvider,
LLMProxyProvider, LiteLLMProvider, LongCatProvider, ManusProvider, MiMoProvider,
MiniMaxProvider, MistralProvider, NanoGPTProvider, NeuralwattProvider, NotionProvider,
OllamaProvider, OpenAIApiProvider, OpenCodeGoProvider, OpenCodeProvider, OpenRouterProvider,
PerplexityProvider, PoeProvider, QoderProvider, QwenCloudProvider, SakanaProvider,
StepFunProvider, Sub2ApiProvider, T3ChatProvider, VeniceProvider, VertexAIProvider,
WarpProvider, WayfinderProvider, WindsurfProvider, XaiProvider, ZaiProvider, ZedProvider,
ZenMuxProvider, ZoomMateProvider,
};

/// Instantiate the concrete [`Provider`] implementation for a given [`ProviderId`].
Expand Down Expand Up @@ -85,6 +86,7 @@ pub fn instantiate(id: ProviderId) -> Box<dyn Provider> {
ProviderId::Zed => Box::new(ZedProvider::new()),
ProviderId::CrossModel => Box::new(CrossModelProvider::new()),
ProviderId::Qoder => Box::new(QoderProvider::new()),
ProviderId::CodeBuddy => Box::new(CodeBuddyProvider::new()),
ProviderId::Sakana => Box::new(SakanaProvider::new()),
ProviderId::Sub2Api => Box::new(Sub2ApiProvider::new()),
ProviderId::Wayfinder => Box::new(WayfinderProvider::new()),
Expand Down
8 changes: 8 additions & 0 deletions rust/src/core/token_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ impl TokenAccountSupport {
requires_manual_cookie_source: true,
cookie_name: None,
}),
ProviderId::CodeBuddy => Some(TokenAccountSupport {
title: "Session tokens",
subtitle: "Store CodeBuddy CN Cookie headers (from plans-usage DevTools cURL).",
placeholder: "Cookie: session=...; ... (or paste full Cookie header)",
injection: TokenInjection::CookieHeader,
requires_manual_cookie_source: true,
cookie_name: None,
}),
ProviderId::Sakana => Some(TokenAccountSupport {
title: "Session tokens",
subtitle: "Store multiple Sakana Console Cookie headers.",
Expand Down
110 changes: 110 additions & 0 deletions rust/src/providers/codebuddy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# CodeBuddy (soft-fork)

Tencent **CodeBuddy CN** credit usage for Win-CodexBar.

## What it shows

- Primary meter: **Credits** used % across all returned resource packages
- Description: compact `remaining / total left` (fits the tray metric row)
- Reset time: earliest package expire time when present

## Setup

1. Settings → Providers → enable **CodeBuddy**
2. Prefer **manual Cookie** (Chrome 127+ App-Bound Encryption often blocks auto-import):

- Open <https://www.codebuddy.cn/profile/plans-usage>
- DevTools → Network → `get-user-resource` → Copy as cURL
- Paste the `Cookie:` value into CodeBuddy settings / token account

3. Or put the cookie on one line in:

```
%USERPROFILE%\.codebuddy\cb_cookie.txt
```

(compatible with `D:\workspace\codebuddy-statusline`)

Auth priority: manual Cookie → `cb_cookie.txt` → browser cookies for
`codebuddy.cn`. Cookies are never logged; only a truncated SHA-256 fingerprint
of the cookie reaches the local cache (see below).

## API

```http
POST https://www.codebuddy.cn/billing/meter/get-user-resource
Origin: https://www.codebuddy.cn
Referer: https://www.codebuddy.cn/profile/plans-usage
User-Agent: Chrome/* (must NOT contain Edg/)
Cookie: …
Content-Type: application/json

{
"PageNumber": 1,
"PageSize": 200,
"ProductCode": "p_tcaca",
"Status": [0, 3],
"OnlyValidPeriod": true,
"PackageCodes": [ "TCACA_code_007_…", … ]
}
```

Sums `CapacitySize* / CapacityUsed* / CapacityRemain*` over `data.Response.Data.Accounts`.

### EdgeOne 401

Tencent EdgeOne rejects Microsoft Edge user-agents on this path. The provider always sends a Chrome UA without `Edg/`.

Expired cookies also surface as 401 — refresh `cb_cookie.txt` / manual cookie.

### Empty Accounts

If `Accounts` is `[]`, your package codes differ. Copy `PackageCodes` from the browser request body and set:

```powershell
$env:CB_PACKAGE_CODES = '["TCACA_code_007_...","TCACA_code_029_..."]'
```

Entries are validated: strings only, trimmed, ≤ 128 chars, no control
characters, at most 64 codes.

### Endpoint override (advanced)

`CB_API_URL` may point the provider at another endpoint (useful for staging or
local mocks). It is validated: must be a well-formed `https:` URL; `http:` is
accepted only for loopback hosts (`localhost`, `127.0.0.1`, `::1`). Invalid
values are ignored with a warning and the default CN endpoint is used.

## Local cache fallback

Source mode **Auto** (transient web failures only) / **CLI** can read:

```
%USERPROFILE%\.codebuddy\cb_credits.json
```

also produced by `codebuddy-statusline`’s `parse_credits.js` pipeline.

Semantics:

- Written only after a **successful** web fetch, from typed numeric totals —
never re-parsed out of the tray's display label.
- Expired/invalid cookies (HTTP 401/403 or an auth-flavoured API response)
propagate as an auth error; they are **never** masked by the cache.
- The cache may carry `accountHash` (truncated SHA-256 of the cookie that
wrote it); a cache belonging to a different account is rejected. Caches
without a fingerprint (external helpers) are accepted.
- Schema: `{ total, used, remaining, source, updatedAt, resetsAt?, accountHash? }`
— values are exact `f64` numbers, `resetsAt` RFC 3339.
- The API response is capped at 8 MiB and the cache file at 1 MiB.

## CLI

```text
codexbar-cli usage -p codebuddy --verbose
```

## Not covered yet

- International `codebuddy.ai` (different host / product codes)
- OAuth device flow (cookie session only)
Loading