Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
00f4bac
feat: port upstream 0.52 project spend
Finesssee Aug 18, 2026
7a2ebf1
feat: complete upstream 0.52 spend parity
Finesssee Aug 18, 2026
8b33dcc
feat: add 0.53 spend accounting contract
Finesssee Aug 19, 2026
cee8bb9
feat: expose 0.53 spend dashboard contract
Finesssee Aug 19, 2026
e922cdc
feat: persist OpenCodex spend import
Finesssee Aug 19, 2026
6699a25
feat: add 0.53 cost json parity
Finesssee Aug 19, 2026
fad82f1
fix: surface selected OpenCode Go auth failures
Finesssee Aug 19, 2026
cf67949
feat: add Grok usage source picker
Finesssee Aug 19, 2026
8dda0d6
fix: preserve Cursor partial cost semantics
Finesssee Aug 19, 2026
ec1535e
feat: add automatic low power preference
Finesssee Aug 19, 2026
7d022bd
feat: add TOON CLI output
Finesssee Aug 19, 2026
d0427a5
feat: show spend context in Overview
Finesssee Aug 19, 2026
9614bdf
fix: route Claude bare model pricing
Finesssee Aug 19, 2026
0cd565b
fix: localize 0.53 spend surfaces
Finesssee Aug 19, 2026
0920e41
fix: dedupe spend locale messages
Finesssee Aug 19, 2026
8d874ac
test: cover automatic low power preference
Finesssee Aug 19, 2026
c3bc06c
fix: harden OpenCodex spend accounting
Finesssee Aug 19, 2026
7d3de92
refactor: unify spend dashboard accounting
Finesssee Aug 19, 2026
1e5fd3c
fix: separate Grok credential sources
Finesssee Aug 19, 2026
796c58f
fix: scope TOON to usage CLI
Finesssee Aug 19, 2026
ab825e8
refactor: canonicalize low power preference
Finesssee Aug 19, 2026
d0f2f09
feat: complete OpenCodex replacement controls
Finesssee Aug 19, 2026
581d943
fix: address 0.53 thermo review
Finesssee Aug 19, 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
29 changes: 14 additions & 15 deletions apps/desktop-tauri/src-tauri/src/auto_refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,22 @@ pub(crate) fn automatic_interval(
}

fn resolve_refresh_interval(settings: &Settings) -> Option<Duration> {
let effective_low_power = settings
.low_power_mode_preference
.resolve(system_battery_saver_enabled());
let requested = if settings.adaptive_refresh {
Some(adaptive_delay_now())
Some(adaptive_delay_now(effective_low_power))
} else {
refresh_interval(settings.refresh_interval_secs)
};
automatic_interval(requested, settings.low_power_mode)
automatic_interval(requested, effective_low_power)
}

fn adaptive_delay_now() -> Duration {
fn adaptive_delay_now(low_power_mode_enabled: bool) -> Duration {
let decision = adaptive_next_delay(AdaptiveRefreshInput {
last_menu_open_age: age_since(&LAST_MENU_OPEN),
last_coding_activity_age: age_since(&LAST_CODING_ACTIVITY),
low_power_mode_enabled: low_power_mode_enabled(),
low_power_mode_enabled,
thermal_pressure: ThermalPressure::Nominal,
});
tracing::debug!(
Expand All @@ -122,13 +125,13 @@ fn adaptive_delay_now() -> Duration {
decision.delay
}

/// Best-effort Windows battery/AC check. Returns false when unknown.
fn low_power_mode_enabled() -> bool {
/// Best-effort Windows Battery Saver check. Returns false when unknown.
fn system_battery_saver_enabled() -> bool {
#[cfg(windows)]
{
// SYSTEM_POWER_STATUS via kernel32. ACLineStatus: 0 = offline (battery).
// BatteryFlag bit 0x08 = charging; BatteryLifePercent 0-100 or 255 unknown.
// Treat "on battery and < 20% remaining" as low-power; skip on failure.
// SYSTEM_POWER_STATUS via kernel32. SystemStatusFlag bit 0x01 is
// the Windows 10+ Battery Saver state. Automatic follows that explicit
// system preference only; battery percentage alone is not equivalent.
#[repr(C)]
struct SystemPowerStatus {
ac_line_status: u8,
Expand All @@ -154,11 +157,7 @@ fn low_power_mode_enabled() -> bool {
if !ok {
return false;
}
let on_battery = status.ac_line_status == 0;
let low_pct = status.battery_life_percent <= 20;
// system_status_flag bit 0x01 = Battery Saver is on (Win10+)
let battery_saver = status.system_status_flag & 0x01 != 0;
on_battery && (low_pct || battery_saver)
status.system_status_flag & 0x01 != 0
}
#[cfg(not(windows))]
{
Expand Down Expand Up @@ -261,7 +260,7 @@ mod tests {
assert_eq!(automatic_interval(None, true), None);

let settings = Settings {
low_power_mode: true,
low_power_mode_preference: codexbar::settings::LowPowerModePreference::On,
adaptive_refresh: false,
refresh_interval_secs: 300,
..Default::default()
Expand Down
8 changes: 7 additions & 1 deletion apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ pub struct SettingsSnapshot {
adaptive_refresh: bool,
refresh_all_providers_on_menu_open: bool,
low_power_mode: bool,
low_power_mode_preference: &'static str,
start_at_login: bool,
start_minimized: bool,
show_notifications: bool,
Expand Down Expand Up @@ -701,6 +702,8 @@ pub struct SettingsSnapshot {
alibaba_token_plan_region: String,
weekly_progress_work_days: Option<u8>,
cost_summary_display_style: &'static str,
open_codex_usage_logs_enabled: bool,
hide_native_codex_cost_when_open_codex_present: bool,
provider_accent_colors: std::collections::HashMap<String, String>,
}

Expand Down Expand Up @@ -749,7 +752,8 @@ impl From<Settings> for SettingsSnapshot {
refresh_interval_secs: settings.refresh_interval_secs,
adaptive_refresh: settings.adaptive_refresh,
refresh_all_providers_on_menu_open: settings.refresh_all_providers_on_menu_open,
low_power_mode: settings.low_power_mode,
low_power_mode: settings.low_power_mode_preference == codexbar::settings::LowPowerModePreference::On,
low_power_mode_preference: settings.low_power_mode_preference.as_str(),
start_at_login: settings.start_at_login,
start_minimized: settings.start_minimized,
show_notifications: settings.show_notifications,
Expand Down Expand Up @@ -810,6 +814,8 @@ impl From<Settings> for SettingsSnapshot {
cost_summary_display_style: cost_summary_display_style_label(
settings.cost_summary_display_style,
),
open_codex_usage_logs_enabled: settings.open_codex_usage_logs_enabled,
hide_native_codex_cost_when_open_codex_present: settings.hide_native_codex_cost_when_open_codex_present,
provider_accent_colors: settings
.provider_configs
.iter()
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod chart;
mod tokens;
mod updater;
mod usage_spend;
mod spend_contract;

mod agent_sessions;
mod bridge;
Expand Down Expand Up @@ -67,6 +68,7 @@ pub use chart::*;
pub use tokens::*;
pub use updater::*;
pub use usage_spend::*;
pub use spend_contract::*;

const PROVIDER_CACHE_STALE_AFTER: std::time::Duration = std::time::Duration::from_secs(30);
const MAX_API_KEY_LEN: usize = 16 * 1024;
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/provider_detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub struct ProviderDetail {
// Phase 6c — currently-persisted cookie source & region for round-tripping
// into the settings UI pickers. `None` for providers that do not support
// one of the pickers.
pub usage_source: Option<String>,
pub cookie_source: Option<String>,
pub region: Option<String>,
}
Expand Down Expand Up @@ -96,6 +97,7 @@ pub(crate) fn build_provider_detail(provider_id: &str) -> Result<ProviderDetail,
None
},
has_snapshot: false,
usage_source: provider_usage_source_lookup(&settings, id.cli_name()),
cookie_source: provider_cookie_source_lookup(&settings, id.cli_name()),
region: provider_region_lookup(&settings, id.cli_name()),
})
Expand Down
55 changes: 55 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/provider_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,37 @@ pub fn reorder_providers(
Ok(build_provider_summaries(&settings))
}

// ── Per-provider usage source ─────────────────────────────────────────

pub(crate) fn provider_usage_source_lookup(
settings: &Settings,
provider_id: &str,
) -> Option<String> {
parse_provider_arg(provider_id)
.ok()
.map(|id| settings.usage_source(id).to_string())
}

#[tauri::command]
pub fn set_provider_usage_source(provider_id: String, source: String) -> Result<(), String> {
let id = parse_provider_arg(&provider_id)?;
let mode = SourceMode::parse(source.trim())
.ok_or_else(|| format!("Invalid usage source '{source}' for provider '{provider_id}'"))?;
let provider = instantiate_provider(id);
if !provider.available_sources().contains(&mode) {
return Err(format!("Usage source '{source}' is unavailable for provider '{provider_id}'"));
}
let value = match mode {
SourceMode::Auto => "auto",
SourceMode::Cli => "cli",
SourceMode::OAuth => "oauth",
SourceMode::Web => "web",
};
let mut settings = Settings::load();
settings.set_usage_source(id, value);
settings.save().map_err(|e| e.to_string())
}

// ── Per-provider cookie source + region ───────────────────────────────

/// Map a CLI-name string to a `ProviderId` whose cookie source is exposed in
Expand All @@ -86,6 +117,7 @@ fn cookie_source_provider(provider_id: &str) -> Option<codexbar::core::ProviderI
"codebuddy" => ProviderId::CodeBuddy,
"sakana" => ProviderId::Sakana,
"notion" => ProviderId::Notion,
"grok" => ProviderId::Grok,
_ => return None,
})
}
Expand Down Expand Up @@ -430,6 +462,29 @@ pub fn cookie_source_options_for(provider_id: &str, lang: Language) -> Vec<Cooki
None,
),
],
"grok" => vec![
cookie_option(
lang,
"auto",
"Automatic imports grok.com browser cookies.",
"Paste a Cookie header from a grok.com request.",
Some("Grok browser cookies are disabled."),
),
cookie_option(
lang,
"manual",
"",
"Paste a Cookie header from a grok.com request.",
None,
),
cookie_option(
lang,
"off",
"",
"",
Some("Grok browser cookies are disabled."),
),
],
"opencode" => vec![
cookie_option(
lang,
Expand Down
26 changes: 25 additions & 1 deletion apps/desktop-tauri/src-tauri/src/commands/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct SettingsUpdate {
pub adaptive_refresh: Option<bool>,
pub refresh_all_providers_on_menu_open: Option<bool>,
pub low_power_mode: Option<bool>,
pub low_power_mode_preference: Option<String>,
pub start_at_login: Option<bool>,
pub start_minimized: Option<bool>,
pub show_notifications: Option<bool>,
Expand Down Expand Up @@ -71,6 +72,8 @@ pub struct SettingsUpdate {
pub alibaba_token_plan_region: Option<String>,
pub weekly_progress_work_days: Option<u8>,
pub cost_summary_display_style: Option<String>,
pub open_codex_usage_logs_enabled: Option<bool>,
pub hide_native_codex_cost_when_open_codex_present: Option<bool>,
}

impl SettingsUpdate {
Expand All @@ -85,6 +88,7 @@ impl SettingsUpdate {
self.enabled_providers.is_some()
|| self.refresh_interval_secs.is_some()
|| self.low_power_mode.is_some()
|| self.low_power_mode_preference.is_some()
|| self.adaptive_refresh.is_some()
|| self.codex_custom_sessions_dirs.is_some()
|| self.high_usage_threshold.is_some()
Expand Down Expand Up @@ -148,8 +152,23 @@ impl SettingsUpdate {
if let Some(v) = self.refresh_all_providers_on_menu_open {
settings.refresh_all_providers_on_menu_open = v;
}
if let Some(v) = self.open_codex_usage_logs_enabled {
settings.open_codex_usage_logs_enabled = v;
}
if let Some(v) = self.hide_native_codex_cost_when_open_codex_present {
settings.hide_native_codex_cost_when_open_codex_present = v;
}
if let Some(v) = self.low_power_mode {
settings.low_power_mode = v;
settings.low_power_mode_preference = if v {
codexbar::settings::LowPowerModePreference::On
} else {
codexbar::settings::LowPowerModePreference::Off
};
}
if let Some(value) = self.low_power_mode_preference.as_deref()
&& let Some(preference) = codexbar::settings::LowPowerModePreference::parse(value)
{
settings.low_power_mode_preference = preference;
}
if let Some(ref s) = self.tray_icon_mode
&& let Some(mode) = parse_tray_icon_mode(s)
Expand Down Expand Up @@ -353,6 +372,11 @@ impl SettingsUpdate {
}

fn apply_to(self, settings: &mut Settings) -> Result<crate::floatbar::SettingsPatch, String> {
if let Some(value) = self.low_power_mode_preference.as_deref()
&& codexbar::settings::LowPowerModePreference::parse(value).is_none()
{
return Err(format!("Invalid low power mode preference: {value}"));
}
let float_bar_patch = self.float_bar_patch();
self.apply_provider_settings(settings)
.apply_general_settings(settings)?
Expand Down
37 changes: 37 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/spend_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Upstream 0.53 Usage & Spend accounting bridge.

use codexbar::settings::Settings;
use codexbar::spend_contract::{SpendContract, build_local_spend_contract_from_summary};
use codexbar::cost_scanner::CostScanner;

#[tauri::command]
pub async fn get_spend_contract(
provider_id: String,
history_days: Option<u32>,
include_open_codex: Option<bool>,
) -> Result<SpendContract, String> {
let provider = provider_id.trim().to_ascii_lowercase();
if !matches!(provider.as_str(), "codex" | "claude" | "opencodego") {
return Err(format!("Spend contract is unavailable for provider: {provider}"));
}
let days = history_days.unwrap_or(30);
let include_import = include_open_codex.unwrap_or(false) && provider == "codex";
tauri::async_runtime::spawn_blocking(move || {
let history_days = if days == 0 { 365 } else { days.clamp(1, 365) };
let scanner = CostScanner::new(history_days);
let summary = match provider.as_str() {
"codex" => scanner.scan_codex(),
"claude" => scanner.scan_claude(),
"opencodego" => scanner.scan_opencodego_with_cancel(None),
_ => unreachable!(),
};
let settings = Settings::load();
build_local_spend_contract_from_summary(
&provider, history_days, include_import,
settings.hide_native_codex_cost_when_open_codex_present && provider == "codex",
summary,
)
})
.await
.map_err(|error| format!("spend contract worker failed: {error}"))
}
Loading
Loading