Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Both jobs use CircleCI's hosted Windows executor (`circleci/windows@5.0`).
2. The credential-free build validates the canonical remote, full tag SHA,
tag-to-SHA identity, protected `main` ancestry, and every project version
file. It provisions/asserts Node 24.x via the `OpenJS.NodeJS.LTS` winget
package, pnpm 11.24.0, the Rust MSVC target, Git, and Inno Setup 6.
package, pnpm 11.25.0, the Rust MSVC target, Git, and Inno Setup 6.
3. It uses a new temporary `WorkRoot`, runs `release-doctor.ps1`, then runs
`windows-release-build.ps1` with the immutable SHA and `-SmokeInstall`.
It never uploads. Six assets — `CodexBar-<version>-Setup.exe` and its
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pnpm run tauri:build

## Runtime/Tooling Preferences

- Package manager: **pnpm@11.24.0** (`packageManager` in `apps/desktop-tauri/package.json` + lockfile). Do not introduce npm or yarn lockfiles.
- Package manager: **pnpm@11.25.0** (`packageManager` in `apps/desktop-tauri/package.json` + lockfile). Do not introduce npm or yarn lockfiles.
- Node: CircleCI pins **Node 24.18.0**; no `.nvmrc` in repo. Prefer Node 24.18.0 locally for hosted parity.
- Rust: edition **2024**, stable toolchain; CI target `x86_64-pc-windows-msvc`. No committed `rust-toolchain.toml` / `rustfmt.toml` / `clippy.toml` — defaults plus CI flags (`clippy -- -D warnings`).
- Tray / DPAPI / browser-cookie behavior: validate on **Windows-native** hosts. WSL/Linux is insufficient for those paths.
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop-tauri/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "desktop-tauri",
"private": true,
"version": "0.55.0",
"packageManager": "pnpm@11.24.0",
"packageManager": "pnpm@11.25.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
10 changes: 6 additions & 4 deletions apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,12 @@ impl ProviderUsageSnapshot {
.unwrap_or_else(|| metadata.session_label.to_string()),
),
secondary: secondary_snap,
secondary_label: usage
.secondary
.as_ref()
.map(|_| metadata.weekly_label.to_string()),
secondary_label: usage.secondary.as_ref().map(|_| {
usage
.secondary_label
.clone()
.unwrap_or_else(|| metadata.weekly_label.to_string())
}),
model_specific: usage
.model_specific
.as_ref()
Expand Down
21 changes: 12 additions & 9 deletions apps/desktop-tauri/src-tauri/src/commands/usage_spend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,15 @@ fn build_usage_spend_summary(
let include_opencodex = settings.open_codex_usage_logs_enabled;
let hide_native = settings.hide_native_codex_cost_when_open_codex_present;

let codex_cache =
codexbar::core::JsonlScanner::load_cache(codexbar::core::ProviderId::Codex, None);
let codex_stale = !codex_cache.days.is_empty() && codex_cache.previous_report.is_some();
let codex_cache_status =
codexbar::core::JsonlScanner::load_cache_status(codexbar::core::ProviderId::Codex, None);
let codex_stale = codex_cache_status.has_days && codex_cache_status.previous_report.is_some();
let codex_stale_updated_at = codex_stale
.then(|| {
codex_cache
codex_cache_status
.previous_report
.as_ref()
.and_then(|r| r.updated_at.clone())
.and_then(|report| report.updated_at.clone())
})
.flatten();

Expand Down Expand Up @@ -351,13 +351,16 @@ fn build_usage_spend_summary(
spend
}
"antigravity" => {
use codexbar::providers::antigravity::local_sessions::LocalHistoryCoverage;
let seven = codexbar::providers::antigravity::local_sessions::summarize(7);
let thirty = codexbar::providers::antigravity::local_sessions::summarize(30);
let mut spend = cached_spend(cached_snapshot);
spend.seven_day_tokens = (seven.session_count > 0).then_some(seven.total_tokens);
spend.thirty_day_tokens = (thirty.session_count > 0).then_some(thirty.total_tokens);
if thirty.session_count > 0 {
spend.source = "local Antigravity sessions".to_string();
spend.seven_day_tokens = matches!(seven.coverage, LocalHistoryCoverage::Complete)
.then_some(seven.total_tokens);
spend.thirty_day_tokens = matches!(thirty.coverage, LocalHistoryCoverage::Complete)
.then_some(thirty.total_tokens);
if matches!(thirty.coverage, LocalHistoryCoverage::Complete) {
spend.source = "local Antigravity history".to_string();
}
spend
}
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/components/MenuCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ describe("MenuCard", () => {
tauriMocks.getLocaleStrings.mockResolvedValue(
buildBundle({
ActionCopyError: "Copy error",
ApiSpendTitle: "API spend",
DetailPaceRunsOutIn: "Runs out in",
PanelEstimatedFromLocalLogs: "Estimated from local logs",
PanelLeftSuffix: "left",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop-tauri/src/components/MenuCardDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ export default function MenuCardDetails({
<section className="menu-card__group menu-card__cost">
<div className="menu-card__group-title">
{provider.cost.alwaysVisible === true && (provider.cost.limit ?? 0) <= 0
? "API spend"
? t("ApiSpendTitle")
: provider.cost.balance != null && provider.cost.limit == null
? provider.cost.period || t("CreditsLabel")
: `${t("DetailCostTitle")} — ${provider.cost.period}`}
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/i18n/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ export const ALL_LOCALE_KEYS = [
"DetailPaceRunsOutIn",
"DetailPaceWillLastToReset",
"DetailCostTitle",
"ApiSpendTitle",
"DetailCostUsed",
"DetailCostLimit",
"DetailCostRemaining",
Expand Down
22 changes: 20 additions & 2 deletions rust/src/cli/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,10 @@ fn append_usage_window_lines(
append_secondary_window_line(
lines,
usage.secondary.as_ref(),
metadata.weekly_label,
usage
.secondary_label
.as_deref()
.unwrap_or(metadata.weekly_label),
use_color,
);
append_model_specific_line(lines, usage.model_specific.as_ref(), use_color);
Expand Down Expand Up @@ -671,7 +674,10 @@ pub fn render_brief_text(provider: ProviderId, result: &ProviderFetchResult) ->
if let Some(secondary) = &usage.secondary {
parts.push(format!(
"{} {}",
metadata.weekly_label,
usage
.secondary_label
.as_deref()
.unwrap_or(metadata.weekly_label),
format_percent(secondary.used_percent)
));
}
Expand Down Expand Up @@ -810,6 +816,18 @@ mod tests {
);
}

#[test]
fn secondary_label_override_is_shared_by_full_and_brief_renderers() {
let result = fetch_result(
UsageSnapshot::new(RateWindow::new(10.0))
.with_secondary(RateWindow::new(20.0))
.with_secondary_label("Weekly"),
);
let full = render_text_with_status(ProviderId::Antigravity, &result, None, false);
let brief = render_brief_text(ProviderId::Antigravity, &result);
assert!(full.contains("Weekly:"));
assert!(brief.contains("Weekly 20%"));
}
#[test]
fn primary_label_override_is_shared_by_full_and_brief_renderers() {
let result =
Expand Down
88 changes: 85 additions & 3 deletions rust/src/core/jsonl_scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,53 @@ use std::fs::{self, File};
use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Default)]
pub struct CachedCostReadStatus {
pub has_days: bool,
pub previous_report: Option<CachedCostReport>,
}

#[derive(Deserialize, Default)]
struct CachedCostReadStatusProjection {
#[serde(
default,
rename = "days",
deserialize_with = "deserialize_nonempty_object"
)]
has_days: bool,
#[serde(default)]
previous_report: Option<CachedCostReport>,
}

fn deserialize_nonempty_object<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{IgnoredAny, MapAccess, Visitor};

struct NonemptyObjectVisitor;

impl<'de> Visitor<'de> for NonemptyObjectVisitor {
type Value = bool;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a JSON object")
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut nonempty = false;
while map.next_entry::<IgnoredAny, IgnoredAny>()?.is_some() {
nonempty = true;
}
Ok(nonempty)
}
}

deserializer.deserialize_map(NonemptyObjectVisitor)
}
/// Maximum retained Codex JSONL line size (upstream session-metadata bound).
const CODEX_JSONL_MAX_LINE_BYTES: usize = 256 * 1024;

Expand Down Expand Up @@ -1059,6 +1106,39 @@ impl JsonlScanner {
CostUsageCache::default()
}

/// Read only the cache metadata needed by presentation surfaces.
///
/// v0.56.0 performance parity: skip raw per-file scanner state and day
/// payloads when callers only need stale/catch-up status.
pub fn load_cache_status(
provider: ProviderId,
cache_root: Option<&Path>,
) -> CachedCostReadStatus {
let cache_path = Self::cache_path(provider, cache_root);
if crate::core::is_bounded_provider(provider) {
#[allow(
clippy::cast_possible_truncation,
reason = "bounded artifacts fit usize on any supported target"
)]
let file_bytes = crate::core::artifact_file_size(&cache_path) as usize;
if file_bytes > crate::core::CostUsageCacheBudget::MAX_LOAD_BYTES {
return CachedCostReadStatus::default();
}
}

let Ok(file) = File::open(cache_path) else {
return CachedCostReadStatus::default();
};
let Ok(projection) =
serde_json::from_reader::<_, CachedCostReadStatusProjection>(BufReader::new(file))
else {
return CachedCostReadStatus::default();
};
CachedCostReadStatus {
has_days: projection.has_days,
previous_report: projection.previous_report,
}
}
fn cached_cost_report_from_days(cache: &CostUsageCache) -> CachedCostReport {
let mut total_cost_usd = 0.0;
let mut input_tokens = 0_i32;
Expand All @@ -1076,9 +1156,11 @@ impl JsonlScanner {
cached_tokens = cached_tokens.saturating_add(cached);
output_tokens = output_tokens.saturating_add(output);

if CostUsagePricing::is_codex_unattributed_model(model)
|| !CostUsagePricing::counts_toward_codex_subscription(model)
{
if CostUsagePricing::is_codex_unattributed_model(model) {
partial = true;
continue;
}
if !CostUsagePricing::counts_toward_codex_subscription(model) {
continue;
}
let priced = pricing_day
Expand Down
12 changes: 12 additions & 0 deletions rust/src/core/usage_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ pub struct UsageSnapshot {
#[serde(skip_serializing_if = "Option::is_none")]
pub secondary: Option<RateWindow>,

/// Provider-resolved label for the secondary rate window when metadata
/// describes a model family rather than this snapshot's cadence.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub secondary_label: Option<String>,

/// Model-specific rate window (e.g., Opus quota for Claude)
#[serde(skip_serializing_if = "Option::is_none")]
pub model_specific: Option<RateWindow>,
Expand Down Expand Up @@ -120,6 +125,7 @@ impl UsageSnapshot {
primary,
primary_label: None,
secondary: None,
secondary_label: None,
model_specific: None,
tertiary: None,
extra_rate_windows: Vec::new(),
Expand All @@ -142,6 +148,12 @@ impl UsageSnapshot {
self
}

/// Builder pattern: override the secondary window label for this snapshot.
pub fn with_secondary_label(mut self, label: impl Into<String>) -> Self {
self.secondary_label = Some(label.into());
self
}

/// Builder pattern: set model-specific window
pub fn with_model_specific(mut self, model_specific: RateWindow) -> Self {
self.model_specific = Some(model_specific);
Expand Down
1 change: 1 addition & 0 deletions rust/src/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,7 @@ locale_keys! {
DetailPaceRunsOutIn,
DetailPaceWillLastToReset,
DetailCostTitle,
ApiSpendTitle,
DetailCostUsed,
DetailCostLimit,
DetailCostRemaining,
Expand Down
1 change: 1 addition & 0 deletions rust/src/locale/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ DetailPaceFarBehind = Far behind
DetailPaceRunsOutIn = Runs out in
DetailPaceWillLastToReset = Will last to reset
DetailCostTitle = Cost
ApiSpendTitle = API spend
DetailCostUsed = Used
DetailCostLimit = Limit
DetailCostRemaining = Remaining
Expand Down
1 change: 1 addition & 0 deletions rust/src/locale/es-MX.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ DetailPaceFarBehind = Muy atrasado
DetailPaceRunsOutIn = Se agota en
DetailPaceWillLastToReset = Durará hasta el reinicio
DetailCostTitle = Costo
ApiSpendTitle = Gasto de API
DetailCostUsed = Usado
DetailCostLimit = Límite
DetailCostRemaining = Restante
Expand Down
1 change: 1 addition & 0 deletions rust/src/locale/ja-JP.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ DetailPaceFarBehind = 大きく遅れ
DetailPaceRunsOutIn = 残り
DetailPaceWillLastToReset = リセットまで持ちます
DetailCostTitle = コスト
ApiSpendTitle = API 費用
DetailCostUsed = 使用済み
DetailCostLimit = 上限
DetailCostRemaining = 残り
Expand Down
1 change: 1 addition & 0 deletions rust/src/locale/ko-KR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ DetailPaceFarBehind = 매우 느림
DetailPaceRunsOutIn = 소진까지
DetailPaceWillLastToReset = 초기화까지 유지 예상
DetailCostTitle = 비용
ApiSpendTitle = API 비용
DetailCostUsed = 사용량
DetailCostLimit = 한도
DetailCostRemaining = 남음
Expand Down
1 change: 1 addition & 0 deletions rust/src/locale/ru-RU.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ DetailPaceFarBehind = Далеко позади
DetailPaceRunsOutIn = заканчивается в
DetailPaceWillLastToReset = Продлится сброс
DetailCostTitle = Стоимость
ApiSpendTitle = Расходы на API
DetailCostUsed = Б/у
DetailCostLimit = Лимит
DetailCostRemaining = Осталось
Expand Down
3 changes: 2 additions & 1 deletion rust/src/locale/tr-TR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ ActionRefresh = Yenile
ActionSwitchAccount = Hesap değiştir...
ActionUsageDashboard = Kullanım panosu
ActionStatusPage = Durum sayfası
ActionCopyError = Kopyalama hatası
ActionCopyError = Hatayı kopyala
ActionBuyCredits = Kredi satın al...
PaceOnTrack = Planlandığı gibi
PaceBehind = Geride
Expand Down Expand Up @@ -508,6 +508,7 @@ DetailPaceFarBehind = Çok geride
DetailPaceRunsOutIn = Tükenmesine kalan
DetailPaceWillLastToReset = Sıfırlamaya kadar yeter
DetailCostTitle = Maliyet
ApiSpendTitle = API harcaması
DetailCostUsed = Kullanılan
DetailCostLimit = Sınır
DetailCostRemaining = Kalan
Expand Down
1 change: 1 addition & 0 deletions rust/src/locale/zh-CN.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ DetailPaceFarBehind = 远落后
DetailPaceRunsOutIn = 预计耗尽时间
DetailPaceWillLastToReset = 足以支撑到重置
DetailCostTitle = 费用
ApiSpendTitle = API 花费
DetailCostUsed = 已用
DetailCostLimit = 限额
DetailCostRemaining = 剩余
Expand Down
1 change: 1 addition & 0 deletions rust/src/locale/zh-TW.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ DetailPaceFarBehind = 遠落後
DetailPaceRunsOutIn = 預計耗盡時間
DetailPaceWillLastToReset = 足以支撐到重置
DetailCostTitle = 費用
ApiSpendTitle = API 花費
DetailCostUsed = 已用
DetailCostLimit = 限額
DetailCostRemaining = 剩餘
Expand Down
Loading