mirror of
https://github.com/tiennm99/claude-code-usage-bubble.git
synced 2026-10-23 14:14:47 +00:00
Merge branch 'worktree-wf_093aad93-ad0-7' into review-fixes
This commit is contained in:
commit
30446570c9
3 files changed
+63
-7
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -267,7 +267,7 @@ pub fn build(input: MenuInput<'_>) -> Vec<MenuEntry> {
|
||||
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`.
|
||||
|
||||
Reference in new issue
Block a user