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
3 changes: 3 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,7 @@ pub struct SettingsSnapshot {
float_bar_show_cost: bool,
promote_tray_icon: bool,
claude_daily_routines_usage_visible: bool,
claude_allow_reading_claude_code_credentials: bool,
alibaba_token_plan_region: String,
weekly_progress_work_days: Option<u8>,
cost_summary_display_style: &'static str,
Expand Down Expand Up @@ -810,6 +811,8 @@ impl From<Settings> for SettingsSnapshot {
float_bar_show_cost: settings.float_bar_show_cost,
promote_tray_icon: settings.promote_tray_icon,
claude_daily_routines_usage_visible: settings.claude_daily_routines_usage_visible,
claude_allow_reading_claude_code_credentials: settings
.claude_allow_reading_claude_code_credentials,
alibaba_token_plan_region: settings.alibaba_token_plan_region,
weekly_progress_work_days: settings.weekly_progress_work_days,
cost_summary_display_style: cost_summary_display_style_label(
Expand Down
32 changes: 32 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub struct SettingsUpdate {
pub tray_scale_percent: Option<u16>,
pub powertoys_status_pipe_enabled: Option<bool>,
pub claude_avoid_keychain_prompts: Option<bool>,
pub claude_allow_reading_claude_code_credentials: Option<bool>,
pub codex_spark_usage_visible: Option<bool>,
pub disable_keychain_access: Option<bool>,
/// Map of provider CLI name → metric preference label.
Expand Down Expand Up @@ -80,6 +81,7 @@ impl SettingsUpdate {
fn refreshes_provider_data(&self) -> bool {
self.enabled_providers.is_some()
|| self.claude_daily_routines_usage_visible.is_some()
|| self.claude_allow_reading_claude_code_credentials.is_some()
|| self.alibaba_token_plan_region.is_some()
|| self.weekly_progress_work_days.is_some()
}
Expand Down Expand Up @@ -324,6 +326,9 @@ impl SettingsUpdate {
if let Some(v) = self.claude_avoid_keychain_prompts {
settings.set_claude_avoid_keychain_prompts(v);
}
if let Some(v) = self.claude_allow_reading_claude_code_credentials {
settings.claude_allow_reading_claude_code_credentials = v;
}
if let Some(v) = self.codex_spark_usage_visible {
settings.set_codex_spark_usage_visible(v);
}
Expand Down Expand Up @@ -516,6 +521,13 @@ mod tests {
}
.refreshes_provider_data()
);
assert!(
SettingsUpdate {
claude_allow_reading_claude_code_credentials: Some(true),
..Default::default()
}
.refreshes_provider_data()
);
assert!(
!SettingsUpdate {
provider_metrics: Some(Default::default()),
Expand All @@ -526,6 +538,26 @@ mod tests {
);
}

#[test]
fn apply_advanced_settings_sets_claude_code_credentials_consent() {
let mut settings = Settings::default();
assert!(!settings.claude_allow_reading_claude_code_credentials);

SettingsUpdate {
claude_allow_reading_claude_code_credentials: Some(true),
..Default::default()
}
.apply_advanced_settings(&mut settings);
assert!(settings.claude_allow_reading_claude_code_credentials);

SettingsUpdate {
claude_allow_reading_claude_code_credentials: Some(false),
..Default::default()
}
.apply_advanced_settings(&mut settings);
assert!(!settings.claude_allow_reading_claude_code_credentials);
}

#[test]
fn display_settings_that_affect_tray_trigger_presentation_refresh() {
assert!(
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function settings(overrides: Partial<SettingsSnapshot> = {}): SettingsSnapshot {
floatBarShowResetInline: false,
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
costSummaryDisplayStyle: "compact",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/floatbar/FloatBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ function settings(overrides: Partial<SettingsSnapshot> = {}): SettingsSnapshot {
floatBarShowResetInline: false,
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
costSummaryDisplayStyle: "compact",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/floatbar/SettingsSection.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const settings = {
floatBarStyle: "floating",
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
floatBarShowResetInline: false,
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src/i18n/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ export const ALL_LOCALE_KEYS = [
"ProviderClaudeAvoidKeychainPromptsHelp",
"ProviderClaudeDailyRoutinesUsage",
"ProviderClaudeDailyRoutinesUsageHelp",
"ProviderClaudeAllowReadingClaudeCodeCredentials",
"ProviderClaudeAllowReadingClaudeCodeCredentialsHelp",
"ProviderCodexSparkUsage",
"ProviderCodexSparkUsageHelp",
"CodexAccountsTitle",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/surfaces/PopOutPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ function settings(): SettingsSnapshot {
floatBarShowResetInline: false,
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
costSummaryDisplayStyle: "compact",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/surfaces/TrayPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ function settings(overrides: Partial<SettingsSnapshot> = {}): SettingsSnapshot {
floatBarShowResetInline: false,
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
costSummaryDisplayStyle: "compact",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { SettingsSnapshot } from "../../../../../types/bridge";
import { ClaudeCreds } from "./ClaudeCreds";

const tauriMocks = vi.hoisted(() => ({
getSettingsSnapshot: vi.fn(),
updateSettings: vi.fn(),
}));

vi.mock("../../../../../lib/tauri", async (importOriginal) => ({
...(await importOriginal<typeof import("../../../../../lib/tauri")>()),
...tauriMocks,
}));

type ClaudeSettingsSnapshot = Pick<
SettingsSnapshot,
| "claudeAvoidKeychainPrompts"
| "claudeDailyRoutinesUsageVisible"
| "claudeAllowReadingClaudeCodeCredentials"
>;

function snapshot(
overrides: Partial<ClaudeSettingsSnapshot> = {},
): ClaudeSettingsSnapshot {
return {
claudeAvoidKeychainPrompts: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
...overrides,
};
}

describe("ClaudeCreds", () => {
beforeEach(() => {
vi.clearAllMocks();
});

it("renders the Claude Code credentials consent toggle, off by default", async () => {
tauriMocks.getSettingsSnapshot.mockResolvedValue(snapshot());

render(<ClaudeCreds t={(key) => key} />);

const checkbox = await screen.findByRole("checkbox", {
name: "ProviderClaudeAllowReadingClaudeCodeCredentials ProviderClaudeAllowReadingClaudeCodeCredentialsHelp",
});
expect(checkbox).not.toBeChecked();
expect(
screen.getByText("ProviderClaudeAllowReadingClaudeCodeCredentialsHelp"),
).toBeInTheDocument();
});

it("toggling the consent checkbox calls updateSettings and reflects the response", async () => {
tauriMocks.getSettingsSnapshot.mockResolvedValue(snapshot());
tauriMocks.updateSettings.mockResolvedValue(
snapshot({ claudeAllowReadingClaudeCodeCredentials: true }),
);

render(<ClaudeCreds t={(key) => key} />);

const checkbox = await screen.findByRole("checkbox", {
name: "ProviderClaudeAllowReadingClaudeCodeCredentials ProviderClaudeAllowReadingClaudeCodeCredentialsHelp",
});

fireEvent.click(checkbox);

await waitFor(() =>
expect(tauriMocks.updateSettings).toHaveBeenCalledWith({
claudeAllowReadingClaudeCodeCredentials: true,
}),
);
await waitFor(() => expect(checkbox).toBeChecked());
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ interface Props {
*
* Port of the `ProviderId::Claude` branch of the "Options" block in
* `rust/src/native_ui/preferences.rs::render_provider_detail_panel`.
* Exposes "Avoid keychain prompts" and "Show Daily Routines usage".
* Exposes "Avoid keychain prompts", "Show Daily Routines usage", and
* "Allow reading Claude Code's credentials" (OAuth consent gate added in
* 76d3f010 — this toggle is the UI surface for that setting).
* The broader `disable_keychain_access` master switch lives in Advanced.
*/
export function ClaudeCreds({ t }: Props) {
const [avoidKeychain, setAvoidKeychain] = useState<boolean | null>(null);
const [showDailyRoutines, setShowDailyRoutines] = useState<boolean | null>(
null,
);
const [allowReadingClaudeCodeCredentials, setAllowReadingClaudeCodeCredentials] =
useState<boolean | null>(null);
const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false);

Expand All @@ -29,6 +33,9 @@ export function ClaudeCreds({ t }: Props) {
if (cancelled) return;
setAvoidKeychain(s.claudeAvoidKeychainPrompts);
setShowDailyRoutines(s.claudeDailyRoutinesUsageVisible ?? true);
setAllowReadingClaudeCodeCredentials(
s.claudeAllowReadingClaudeCodeCredentials ?? false,
);
})
.catch((e) => !cancelled && setError(String(e)));
return () => {
Expand Down Expand Up @@ -64,7 +71,28 @@ export function ClaudeCreds({ t }: Props) {
}
};

if (avoidKeychain === null || showDailyRoutines === null) return null;
const toggleAllowReadingClaudeCodeCredentials = async (next: boolean) => {
setSaving(true);
try {
const updated = await updateSettings({
claudeAllowReadingClaudeCodeCredentials: next,
});
setAllowReadingClaudeCodeCredentials(
updated.claudeAllowReadingClaudeCodeCredentials ?? next,
);
} catch (e) {
setError(String(e));
} finally {
setSaving(false);
}
};

if (
avoidKeychain === null ||
showDailyRoutines === null ||
allowReadingClaudeCodeCredentials === null
)
return null;

return (
<section className="provider-detail-section">
Expand Down Expand Up @@ -101,6 +129,24 @@ export function ClaudeCreds({ t }: Props) {
</span>
</span>
</label>
<label className="provider-detail-toggle">
<input
type="checkbox"
checked={allowReadingClaudeCodeCredentials}
disabled={saving}
onChange={(e) =>
void toggleAllowReadingClaudeCodeCredentials(e.target.checked)
}
/>
<span>
<span className="provider-detail-toggle__label">
{t("ProviderClaudeAllowReadingClaudeCodeCredentials")}
</span>
<span className="provider-detail-toggle__helper">
{t("ProviderClaudeAllowReadingClaudeCodeCredentialsHelp")}
</span>
</span>
</label>
{error && <div className="provider-detail-error">{error}</div>}
</section>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const settings: SettingsSnapshot = {
floatBarShowResetInline: false,
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
costSummaryDisplayStyle: "compact",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const settings: SettingsSnapshot = {
floatBarShowResetInline: false,
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
costSummaryDisplayStyle: "compact",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/types/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ describe("Language type", () => {
floatBarShowResetInline: false,
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
costSummaryDisplayStyle: "compact",
Expand Down
8 changes: 8 additions & 0 deletions apps/desktop-tauri/src/types/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ export interface SettingsSnapshot {
promoteTrayIcon?: boolean;
/** When true, show Claude Daily Routines quota row (default true). */
claudeDailyRoutinesUsageVisible: boolean;
/**
* Explicit consent to read (and refresh) Claude Code's own OAuth
* credentials for the Claude provider. Default false — without consent
* OAuth stays closed and Auto falls back to labeled reduced-fidelity CLI
* usage (upstream #2634/#2745).
*/
claudeAllowReadingClaudeCodeCredentials: boolean;
/** Alibaba Token Plan region: cn | intl | cn-personal | intl-personal. */
alibabaTokenPlanRegion: string;
/** Optional work-week length [2,6] for session-equivalent weekly forecast. */
Expand Down Expand Up @@ -305,6 +312,7 @@ export interface SettingsUpdate {
trayScalePercent?: number;
powertoysStatusPipeEnabled?: boolean;
claudeAvoidKeychainPrompts?: boolean;
claudeAllowReadingClaudeCodeCredentials?: boolean;
codexSparkUsageVisible?: boolean;
disableKeychainAccess?: boolean;
/** Map of provider CLI name → metric preference label. */
Expand Down
2 changes: 2 additions & 0 deletions rust/src/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ locale_keys! {
ProviderClaudeAvoidKeychainPromptsHelp,
ProviderClaudeDailyRoutinesUsage,
ProviderClaudeDailyRoutinesUsageHelp,
ProviderClaudeAllowReadingClaudeCodeCredentials,
ProviderClaudeAllowReadingClaudeCodeCredentialsHelp,
ProviderCodexSparkUsage,
ProviderCodexSparkUsageHelp,
CodexAccountsTitle,
Expand Down
2 changes: 2 additions & 0 deletions rust/src/locale/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ ProviderClaudeAvoidKeychainPrompts = Avoid Keychain prompts
ProviderClaudeAvoidKeychainPromptsHelp = Use /usr/bin/security to read Claude credentials and avoid CodexBar keychain prompts.
ProviderClaudeDailyRoutinesUsage = Show Daily Routines usage
ProviderClaudeDailyRoutinesUsageHelp = Show the Daily Routines quota row for Claude web and OAuth usage.
ProviderClaudeAllowReadingClaudeCodeCredentials = Allow reading Claude Code's credentials
ProviderClaudeAllowReadingClaudeCodeCredentialsHelp = Lets CodexBar read (and refresh) Claude Code's own OAuth credentials for higher-fidelity usage. Off by default; without it, Auto falls back to reduced-fidelity CLI usage.
ProviderCodexSparkUsage = Show Codex Spark usage
ProviderCodexSparkUsageHelp = Show Codex Spark quota rows without hiding credits or other extra usage.
CodexAccountsTitle = Codex Accounts
Expand Down
2 changes: 2 additions & 0 deletions rust/src/locale/tr-TR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ ProviderClaudeAvoidKeychainPrompts = Anahtar Zinciri istemlerinden kaçın
ProviderClaudeAvoidKeychainPromptsHelp = Claude kimlik bilgilerini okumak ve CodexBar Anahtar Zinciri istemlerini önlemek için /usr/bin/security kullanın.
ProviderClaudeDailyRoutinesUsage = Günlük Rutinler kullanımını göster
ProviderClaudeDailyRoutinesUsageHelp = Claude web ve OAuth kullanımı için Günlük Rutinler kota satırını göster.
ProviderClaudeAllowReadingClaudeCodeCredentials = Claude Code kimlik bilgilerini okumaya izin ver
ProviderClaudeAllowReadingClaudeCodeCredentialsHelp = CodexBar'ın daha isabetli kullanım verisi için Claude Code'un kendi OAuth kimlik bilgilerini okumasına (ve yenilemesine) izin verir. Varsayılan olarak kapalıdır; kapalıyken Otomatik mod, daha düşük isabetli CLI kullanımına düşer.
ProviderCodexSparkUsage = Codex Spark kullanımını göster
ProviderCodexSparkUsageHelp = Kredileri veya diğer ek kullanımları gizlemeden Codex Spark kota satırlarını göster.
CodexAccountsTitle = Codex Hesapları
Expand Down
1 change: 1 addition & 0 deletions rust/src/settings/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fn test_settings_default() {
assert!(!settings.float_bar_show_cost);
assert!(settings.promote_tray_icon);
assert!(settings.claude_daily_routines_usage_visible);
assert!(!settings.claude_allow_reading_claude_code_credentials);
assert_eq!(
settings.low_power_mode_preference,
LowPowerModePreference::Off
Expand Down
Loading