Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
548660a
Preserve Codex reset candidates across credit refresh
Finesssee Sep 6, 2026
04aa375
Port v0.56.2 cost history fixes
Finesssee Sep 6, 2026
439e54a
Honor agent metadata scan deadlines
Finesssee Sep 6, 2026
b4e408f
Keep v0.56.2 history dates readable
Finesssee Sep 6, 2026
5717bc9
Port v0.56.2 provider action semantics
Finesssee Sep 6, 2026
6730173
Match upstream Codex timestamp ordering
Finesssee Sep 6, 2026
b0f0e60
Preserve cost provenance across spend merges
Finesssee Sep 6, 2026
fc3cea2
Bound Codex cost catch-up work
Finesssee Sep 6, 2026
a401456
Port Codex fork parent accounting
Finesssee Sep 6, 2026
cfd8614
Preserve Codex reasoning token detail
Finesssee Sep 6, 2026
3e6788d
Expose native Codex reasoning tokens
Finesssee Sep 6, 2026
ea2dbd4
Preserve reasoning tokens across cache rebuilds
Finesssee Sep 6, 2026
af35242
Decompose spend contract tests
Finesssee Sep 6, 2026
1daefc6
Decompose cost scanner tests
Finesssee Sep 6, 2026
9c54ced
Decompose Codex cost scanner
Finesssee Sep 6, 2026
bb086b6
Decompose JSONL scanner tests
Finesssee Sep 6, 2026
72d359a
Decompose Codex JSONL scanner
Finesssee Sep 6, 2026
efb6232
Decompose Codex JSONL parser internals
Finesssee Sep 6, 2026
a5e8a4b
Fix 0.56.2 native Rust compile regressions
Finesssee Sep 7, 2026
6a17229
Fix 0.56.2 timestamp publication range
Finesssee Sep 7, 2026
be5e9ad
Stabilize 0.56.2 parallel native tests
Finesssee Sep 7, 2026
379dc31
Make 0.56.2 Rust port Clippy-clean
Finesssee Sep 7, 2026
c33e725
refactor: address 0.56.2 thermo review
Finesssee Sep 8, 2026
3b5851c
style: apply workspace rustfmt
Finesssee Sep 8, 2026
c5a41a2
fix: repair 0.56.2 review refactor
Finesssee Sep 8, 2026
11955aa
fix: remove stale pace re-export
Finesssee Sep 8, 2026
52c128c
Merge repaired 0.56.1 base
Finesssee Sep 8, 2026
97836a1
style: format repaired 0.56.2 stack
Finesssee Sep 8, 2026
321ad81
test: mark local OpenCode Go pace non-authoritative
Finesssee Sep 8, 2026
9c58915
Merge remote-tracking branch 'origin/main' into HEAD
Finesssee Sep 8, 2026
041f8b4
refactor: keep 0.56.2 modules below review threshold
Finesssee Sep 8, 2026
4b479aa
style: trim extracted weekly reset tests
Finesssee Sep 8, 2026
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
167 changes: 18 additions & 149 deletions apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
pub(crate) mod pace;
mod status;
pub(crate) use status::{compact_tray_status_label, friendly_provider_error};

use super::*;

// ── Bridge snapshot types ────────────────────────────────────────────
Expand Down Expand Up @@ -254,19 +258,6 @@ pub(crate) fn filter_hidden_codex_spark_rows(
}
}

pub(crate) fn pace_stage_str(stage: codexbar::core::PaceStage) -> &'static str {
use codexbar::core::PaceStage;
match stage {
PaceStage::OnTrack => "on_track",
PaceStage::SlightlyAhead => "slightly_ahead",
PaceStage::Ahead => "ahead",
PaceStage::FarAhead => "far_ahead",
PaceStage::SlightlyBehind => "slightly_behind",
PaceStage::Behind => "behind",
PaceStage::FarBehind => "far_behind",
}
}

impl ProviderUsageSnapshot {
pub(super) fn from_fetch_result(
id: ProviderId,
Expand All @@ -275,6 +266,7 @@ impl ProviderUsageSnapshot {
token_account_id: Option<uuid::Uuid>,
) -> Self {
let usage = &result.usage;
let allows_pace = result.pace_authoritative;

// A missing session is represented by an informational primary so the
// weekly lane keeps its canonical role. Use that weekly lane for the
Expand All @@ -284,11 +276,14 @@ impl ProviderUsageSnapshot {
} else {
Some(&usage.primary)
};
let primary_pace = primary_pace_window
.and_then(|window| codexbar::core::UsagePace::weekly(window, None, 10080));
let primary_pace = allows_pace.then(|| {
primary_pace_window
.and_then(|window| codexbar::core::UsagePace::weekly(window, None, 10080))
});
let primary_pace = primary_pace.flatten();

let pace = primary_pace.as_ref().map(|p| PaceSnapshot {
stage: pace_stage_str(p.stage).to_string(),
stage: pace::stage_str(p.stage).to_string(),
delta_percent: p.delta_percent,
will_last_to_reset: p.will_last_to_reset,
eta_seconds: p.eta_seconds,
Expand All @@ -297,10 +292,13 @@ impl ProviderUsageSnapshot {
});

// Compute pace for secondary window (weekly) to derive reserve info
let secondary_pace = usage
.secondary
.as_ref()
.and_then(|sw| codexbar::core::UsagePace::weekly(sw, None, 10080));
let secondary_pace = allows_pace.then(|| {
usage
.secondary
.as_ref()
.and_then(|sw| codexbar::core::UsagePace::weekly(sw, None, 10080))
});
let secondary_pace = secondary_pace.flatten();

let primary_snap = RateWindowSnapshot::from_rate_window(&usage.primary);

Expand Down Expand Up @@ -521,135 +519,6 @@ fn session_equivalent_forecast_for(
})
}

/// Build a compact tray status label from a raw snapshot using the current language.
/// Localization is done at render time so cached snapshots stay language-neutral.
pub(crate) fn compact_tray_status_label(
window: &RateWindowSnapshot,
lang: codexbar::settings::Language,
) -> String {
if window.is_informational {
return window
.reset_description
.clone()
.unwrap_or_else(|| "Unavailable".to_string());
}

let pct = format!("{:.0}%", window.used_percent);
if let Some(reset) = compact_reset_description(window, lang) {
format!("{pct} • {reset}")
} else {
pct
}
}

fn compact_reset_description(
window: &RateWindowSnapshot,
lang: codexbar::settings::Language,
) -> Option<String> {
if let Some(ref resets_at) = window.resets_at {
let dt = chrono::DateTime::parse_from_rfc3339(resets_at)
.ok()
.map(|dt| dt.with_timezone(&chrono::Utc))?;
return Some(format_compact_reset_countdown(dt, lang));
}

window
.reset_description
.as_deref()
.map(|desc| normalize_reset_description(desc, lang))
.filter(|desc| !desc.is_empty())
}

fn format_compact_reset_countdown(
resets_at: chrono::DateTime<chrono::Utc>,
lang: codexbar::settings::Language,
) -> String {
let now = chrono::Utc::now();
if resets_at <= now {
return locale::get_text(lang, locale::LocaleKey::ResetInProgress);
}

let total_minutes = (resets_at - now).num_minutes().max(0);
let days = total_minutes / 1440;
let hours = (total_minutes % 1440) / 60;
let minutes = total_minutes % 60;

if days > 0 {
locale::format_locale(
lang,
locale::LocaleKey::ResetsInDaysHours,
&[&days.to_string(), &hours.to_string()],
)
} else {
locale::format_locale(
lang,
locale::LocaleKey::ResetsInHoursMinutes,
&[&hours.to_string(), &format!("{minutes:02}")],
)
}
}

fn normalize_reset_description(desc: &str, lang: codexbar::settings::Language) -> String {
let trimmed = desc.trim();
let lower = trimmed.to_ascii_lowercase();
let prefix_len = ["resets in ", "reset in ", "in "]
.iter()
.find(|&&p| lower.starts_with(p))
.map(|p| p.len())
.unwrap_or(0);
let body = trimmed[prefix_len..].trim_start();
format!(
"{} {body}",
locale::get_text(lang, locale::LocaleKey::ResetsInShort)
)
}

pub(crate) fn friendly_provider_error(id: ProviderId, error: &str) -> String {
if id != ProviderId::Claude {
return error.to_string();
}

let trimmed = error.trim();
let lower = trimmed.to_lowercase();

if lower.contains("swift.cancellationerror")
|| lower.contains("the operation couldn't be completed")
|| lower.contains("the operation could not be completed")
{
return "Claude usage fetch was cancelled before usage data was returned. Refresh Claude, or re-authenticate with Claude Code and try again.".to_string();
}

if lower.contains("claude oauth credentials not found") {
return "Claude sign-in was not found. Run `claude` once to authenticate, then refresh Claude in Win-CodexBar.".to_string();
}

if lower.contains("oauth token expired") || lower.contains("token invalid or expired") {
return "Claude sign-in expired. Run `claude` to refresh your Claude Code login, then refresh Claude in Win-CodexBar.".to_string();
}

if trimmed == "Authentication required" {
return "Claude needs sign-in before Win-CodexBar can read usage. Run `claude` once, or add Claude cookies in Provider settings.".to_string();
}

if lower.starts_with("claude usage failed from all configured sources.") {
return trimmed
.replace(
"OAuth: OAuth error: Claude OAuth credentials not found. Run `claude` to authenticate.",
"OAuth: sign-in not found",
)
.replace(
"Web: No cookies available for web API",
"Web: no Claude cookies available",
)
.replace(
"CLI: Provider not installed:",
"CLI: not installed:",
);
}

trimmed.to_string()
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BootstrapState {
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/bridge/pace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pub(crate) fn stage_str(stage: codexbar::core::PaceStage) -> &'static str {
use codexbar::core::PaceStage;
match stage {
PaceStage::OnTrack => "on_track",
PaceStage::SlightlyAhead => "slightly_ahead",
PaceStage::Ahead => "ahead",
PaceStage::FarAhead => "far_ahead",
PaceStage::SlightlyBehind => "slightly_behind",
PaceStage::Behind => "behind",
PaceStage::FarBehind => "far_behind",
}
}
130 changes: 130 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/bridge/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use super::*;

/// Build a compact tray status label from a raw snapshot using the current language.
/// Localization is done at render time so cached snapshots stay language-neutral.
pub(crate) fn compact_tray_status_label(
window: &RateWindowSnapshot,
lang: codexbar::settings::Language,
) -> String {
if window.is_informational {
return window
.reset_description
.clone()
.unwrap_or_else(|| "Unavailable".to_string());
}

let pct = format!("{:.0}%", window.used_percent);
if let Some(reset) = compact_reset_description(window, lang) {
format!("{pct} • {reset}")
} else {
pct
}
}

fn compact_reset_description(
window: &RateWindowSnapshot,
lang: codexbar::settings::Language,
) -> Option<String> {
if let Some(ref resets_at) = window.resets_at {
let dt = chrono::DateTime::parse_from_rfc3339(resets_at)
.ok()
.map(|dt| dt.with_timezone(&chrono::Utc))?;
return Some(format_compact_reset_countdown(dt, lang));
}

window
.reset_description
.as_deref()
.map(|desc| normalize_reset_description(desc, lang))
.filter(|desc| !desc.is_empty())
}

fn format_compact_reset_countdown(
resets_at: chrono::DateTime<chrono::Utc>,
lang: codexbar::settings::Language,
) -> String {
let now = chrono::Utc::now();
if resets_at <= now {
return locale::get_text(lang, locale::LocaleKey::ResetInProgress);
}

let total_minutes = (resets_at - now).num_minutes().max(0);
let days = total_minutes / 1440;
let hours = (total_minutes % 1440) / 60;
let minutes = total_minutes % 60;

if days > 0 {
locale::format_locale(
lang,
locale::LocaleKey::ResetsInDaysHours,
&[&days.to_string(), &hours.to_string()],
)
} else {
locale::format_locale(
lang,
locale::LocaleKey::ResetsInHoursMinutes,
&[&hours.to_string(), &format!("{minutes:02}")],
)
}
}

fn normalize_reset_description(desc: &str, lang: codexbar::settings::Language) -> String {
let trimmed = desc.trim();
let lower = trimmed.to_ascii_lowercase();
let prefix_len = ["resets in ", "reset in ", "in "]
.iter()
.find(|&&p| lower.starts_with(p))
.map(|p| p.len())
.unwrap_or(0);
let body = trimmed[prefix_len..].trim_start();
format!(
"{} {body}",
locale::get_text(lang, locale::LocaleKey::ResetsInShort)
)
}

pub(crate) fn friendly_provider_error(id: ProviderId, error: &str) -> String {
if id != ProviderId::Claude {
return error.to_string();
}

let trimmed = error.trim();
let lower = trimmed.to_lowercase();

if lower.contains("swift.cancellationerror")
|| lower.contains("the operation couldn't be completed")
|| lower.contains("the operation could not be completed")
{
return "Claude usage fetch was cancelled before usage data was returned. Refresh Claude, or re-authenticate with Claude Code and try again.".to_string();
}

if lower.contains("claude oauth credentials not found") {
return "Claude sign-in was not found. Run `claude` once to authenticate, then refresh Claude in Win-CodexBar.".to_string();
}

if lower.contains("oauth token expired") || lower.contains("token invalid or expired") {
return "Claude sign-in expired. Run `claude` to refresh your Claude Code login, then refresh Claude in Win-CodexBar.".to_string();
}

if trimmed == "Authentication required" {
return "Claude needs sign-in before Win-CodexBar can read usage. Run `claude` once, or add Claude cookies in Provider settings.".to_string();
}

if lower.starts_with("claude usage failed from all configured sources.") {
return trimmed
.replace(
"OAuth: OAuth error: Claude OAuth credentials not found. Run `claude` to authenticate.",
"OAuth: sign-in not found",
)
.replace(
"Web: No cookies available for web API",
"Web: no Claude cookies available",
)
.replace(
"CLI: Provider not installed:",
"CLI: not installed:",
);
}

trimmed.to_string()
}
18 changes: 18 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ fn dashboard_url_for_provider(provider_id: &str) -> Option<String> {
);
}

// OpenRouter's Usage Dashboard is the Activity page. Resolve it from the
// provider metadata before the legacy API-key catalog entry, which still
// points at the credits settings page.
if provider_id == ProviderId::OpenRouter.cli_name() {
return instantiate_provider(ProviderId::OpenRouter)
.metadata()
.dashboard_url
.map(|s| s.to_string());
}

if let Some(url) = codexbar::settings::get_api_key_providers()
.into_iter()
.find(|p| p.id.cli_name() == provider_id)
Expand Down Expand Up @@ -381,4 +391,12 @@ mod tests {
Some("https://chatgpt.com/codex/settings/usage")
);
}

#[test]
fn dashboard_url_resolves_openrouter_activity() {
assert_eq!(
dashboard_url_for_provider("openrouter").as_deref(),
Some("https://openrouter.ai/activity")
);
}
}
Loading