diff --git a/README.md b/README.md index d60c779..249ba98 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ self-updater are all written from scratch against the same public APIs - Left-click the bubble for an expanded panel with both **5h** and **7d** bars plus reset countdowns - Right-click for refresh, displayed providers, update frequency, startup, - updates, exit + **Reset position**, updates, **Show widget**, **Restart** and **Exit** - A system tray icon per enabled provider, with a usage badge - Desktop notifications when usage crosses a threshold, when a sign-in expires and after an update was applied @@ -55,7 +55,11 @@ right-click **Providers** menu. - Windows 10 or Windows 11, or Linux x86_64 (see below) - Claude Code (CLI or App) installed and authenticated -- Optional: Codex CLI installed and authenticated, if you want Codex usage +- The `claude` CLI, for automatic token refresh. The app looks for it on + `PATH`, and on Linux also in `~/.local/bin` and `~/.claude/local`. Without + it an expired sign-in is not refreshed and you have to sign in again. +- Optional: Codex CLI installed and authenticated, if you want Codex usage. + Its token is refreshed through the `codex` CLI, found the same way. ### Windows @@ -161,9 +165,11 @@ primary monitor on first launch. Drag it where you want it, release to snap to the nearest edge if you let go close to one. - **Left-click** the bubble to open the expanded panel (5h + 7d + countdowns) -- **Right-click** for refresh, providers, refresh frequency, "Start with - Windows" ("Start at login" on Linux), controls, auto-update check - (Disabled / Hourly / Daily / Weekly), manual "Check for updates", exit +- **Right-click** for **Refresh**, **Update frequency**, **Providers**, + **Settings**, **Controls**, **Show widget**, **Restart** and **Exit**. + **Settings** holds "Start with Windows" ("Start at login" on Linux), + **Reset position**, a manual **Check for updates** and the + **Auto-update check** interval (Disabled / Hourly / Daily / Weekly) - **Drag** anywhere — it floats on top of all other windows - **Ctrl + MouseWheel** on the bubble, or **Controls** in the right-click menu, to resize it @@ -244,13 +250,26 @@ What the app sends over the network: - Requests to Anthropic's Claude endpoints to read your usage - Requests to ChatGPT's Codex usage endpoint, if Codex is enabled -- Requests to GitHub only if you use the app's update-check feature +- Requests for `latest.json` from GitHub Releases, at first launch and then + hourly by default. Set **Settings** → **Auto-update check** to + **Disabled** in the right-click menu to stop them; **Check for updates** + still works when you click it. + +What the app runs: + +- When a usage request is rejected because the sign-in has expired, the app + runs `claude -p .` for Claude Code and `codex exec .` for Codex, for each + provider that was rejected. They run hidden in the background, so the CLI + refreshes its token. Each run sends one tiny prompt, which counts toward + your usage. What the app stores locally: - Bubble position(s) per model - Bubble size +- Whether the bubbles are shown (**Show widget**) - Polling frequency +- Auto-update check interval - Last update check time - Displayed provider preferences diff --git a/src/core/i18n.rs b/src/core/i18n.rs index cc3a35e..917d7e2 100644 --- a/src/core/i18n.rs +++ b/src/core/i18n.rs @@ -38,6 +38,9 @@ pub struct LocaleStrings { pub control_drag: String, pub control_ctrl_wheel: String, pub control_tray_click: String, + /// Linux variant of `control_tray_click`: AppIndicator delivers no tray + /// clicks, so every click opens the menu. + pub control_tray_click_linux: String, pub tray_left_click: String, pub check_for_updates: String, pub checking_for_updates: String, @@ -105,6 +108,17 @@ impl LocaleStrings { } } + /// Controls hint for the tray icon on the platform this build targets. + /// Linux tray icons (AppIndicator) only open the menu, so the + /// show/hide click is not offered there. + pub fn tray_click_hint(&self) -> &str { + if cfg!(target_os = "linux") { + &self.control_tray_click_linux + } else { + &self.control_tray_click + } + } + /// Display name of a provider. pub fn provider_label(&self, provider: ProviderId) -> &str { match provider { @@ -141,6 +155,7 @@ fn english_strings() -> LocaleStrings { control_drag: "Drag: move/snap".into(), control_ctrl_wheel: "Ctrl+Wheel: resize".into(), control_tray_click: "Tray click: show/hide".into(), + control_tray_click_linux: "Tray click: menu".into(), tray_left_click: "Left-click: show/hide".into(), check_for_updates: "Check for updates".into(), checking_for_updates: "Checking for updates...".into(), @@ -286,6 +301,10 @@ mod tests { ("control_drag", strings.control_drag.as_str()), ("control_ctrl_wheel", strings.control_ctrl_wheel.as_str()), ("control_tray_click", strings.control_tray_click.as_str()), + ( + "control_tray_click_linux", + strings.control_tray_click_linux.as_str(), + ), ("tray_left_click", strings.tray_left_click.as_str()), ] { assert!(!value.trim().is_empty(), "empty string: {name}"); @@ -372,4 +391,15 @@ mod tests { assert_eq!(s.autostart_label(), expected); assert_eq!(s.provider_label(ProviderId::ChatGpt), "Codex"); } + + #[test] + fn tray_click_hint_matches_the_platform() { + let s = english_strings(); + let expected = if cfg!(target_os = "linux") { + "Tray click: menu" + } else { + "Tray click: show/hide" + }; + assert_eq!(s.tray_click_hint(), expected); + } } diff --git a/src/core/menu_model.rs b/src/core/menu_model.rs index 452bee8..9087c83 100644 --- a/src/core/menu_model.rs +++ b/src/core/menu_model.rs @@ -267,7 +267,7 @@ pub fn build(input: MenuInput<'_>) -> Vec { hint(&s.control_right_click), hint(&s.control_drag), hint(&s.control_ctrl_wheel), - hint(&s.control_tray_click), + hint(s.tray_click_hint()), ]; vec![ @@ -453,6 +453,13 @@ mod tests { assert!(entries[4..] .iter() .all(|e| matches!(e, MenuEntry::Hint { .. }))); + let strings = I18n::load().strings().clone(); + assert_eq!( + entries[8], + MenuEntry::Hint { + label: strings.tray_click_hint().to_string() + } + ); } /// One line per node, indented by depth: `label [id] kind`.