mirror of
https://github.com/tiennm99/claude-code-usage-bubble.git
synced 2026-10-22 04:14:52 +00:00
Every Rust module under src/ that previously contained upstream-derivative
code has been replaced by a from-scratch implementation:
diag/ log + simplelog file appender (was: diagnose.rs)
os/ color, dpi, registry, string, theme (was: theme.rs, native_interop.rs)
net/ WinHTTP-based HTTP client (was: ureq + native-tls)
i18n/ TOML-embedded locale tables (was: localization/*.rs)
usage/ trait UsageProvider + ClaudeProvider + ChatGptProvider + refresh
orchestrator + registry (was: poller.rs, models.rs)
creds/ trait CredentialSource + local/WSL/Codex impls (was: poller.rs)
tray/ stateless tray manager + tiny-skia anti-aliased badge renderer
(was: tray_icon.rs)
update/ release fetch + inline cmd /c handoff installer
(was: updater.rs's helper-exe pattern)
Application files (app.rs, bubble.rs, panel.rs, settings.rs) migrated to
the new modules. main.rs declares only the new modules.
NOTICE deleted; LICENSE is plain Apache-2.0; README updated to credit
inspiration rather than claim derivation. Cargo.toml drops ureq + native-tls
+ winres in favour of log + simplelog + thiserror + toml + tiny-skia +
embed-resource. Build script swapped to embed-resource via res/icon.rc.
External contracts preserved unchanged: Anthropic + ChatGPT endpoints and
headers, ~/.claude/.credentials.json + Codex auth.json paths, WSL bridging
via wsl.exe, CLI-driven token refresh, GitHub Releases JSON shape, Windows
registry path for startup, single-instance mutex name.
Phase docs: plans/260516-0707-cleanroom-rewrite/.
124 lines
3.6 KiB
Rust
124 lines
3.6 KiB
Rust
// Token refresh orchestrator.
|
|
//
|
|
// Each provider's credential source advertises a `RefreshHint` describing
|
|
// which CLI to spawn. We invoke that CLI and watch the credential file's
|
|
// signature; if it changes within the timeout we declare success.
|
|
|
|
use std::os::windows::process::CommandExt;
|
|
use std::process::{Command, Stdio};
|
|
use std::time::{Duration, Instant};
|
|
|
|
use crate::creds::{CredentialSource, RefreshHint};
|
|
|
|
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum Outcome {
|
|
Refreshed,
|
|
StillExpired,
|
|
CliMissing,
|
|
Timeout,
|
|
}
|
|
|
|
pub struct Orchestrator {
|
|
timeout: Duration,
|
|
poll_interval: Duration,
|
|
}
|
|
|
|
impl Orchestrator {
|
|
pub fn new(timeout: Duration) -> Self {
|
|
Self {
|
|
timeout,
|
|
poll_interval: Duration::from_millis(500),
|
|
}
|
|
}
|
|
|
|
pub fn refresh(&self, source: &dyn CredentialSource) -> Outcome {
|
|
let initial_sig = source.signature();
|
|
let hint = source.refresh_hint();
|
|
if !spawn_cli(&hint) {
|
|
return Outcome::CliMissing;
|
|
}
|
|
let start = Instant::now();
|
|
while start.elapsed() < self.timeout {
|
|
std::thread::sleep(self.poll_interval);
|
|
if source.signature() != initial_sig {
|
|
return Outcome::Refreshed;
|
|
}
|
|
}
|
|
if source.signature() != initial_sig {
|
|
Outcome::Refreshed
|
|
} else {
|
|
Outcome::Timeout
|
|
}
|
|
}
|
|
}
|
|
|
|
fn spawn_cli(hint: &RefreshHint) -> bool {
|
|
match hint {
|
|
RefreshHint::LocalClaudeCli => spawn_local(&["claude.cmd", "claude.exe", "claude"], &["-p", "."]),
|
|
RefreshHint::WslClaudeCli { distro } => spawn_wsl(distro),
|
|
RefreshHint::LocalCodexCli => {
|
|
spawn_local(&["codex.cmd", "codex.ps1", "codex.exe", "codex"], &["exec", "."])
|
|
}
|
|
}
|
|
}
|
|
|
|
fn spawn_local(candidates: &[&str], args: &[&str]) -> bool {
|
|
for name in candidates {
|
|
let lower = name.to_ascii_lowercase();
|
|
let mut cmd = if lower.ends_with(".ps1") {
|
|
let mut c = Command::new("powershell.exe");
|
|
c.arg("-NoProfile").arg("-ExecutionPolicy").arg("Bypass").arg("-File").arg(name);
|
|
for a in args {
|
|
c.arg(a);
|
|
}
|
|
c
|
|
} else if lower.ends_with(".cmd") || lower.ends_with(".bat") {
|
|
let mut c = Command::new("cmd.exe");
|
|
c.arg("/c").arg(name);
|
|
for a in args {
|
|
c.arg(a);
|
|
}
|
|
c
|
|
} else {
|
|
let mut c = Command::new(name);
|
|
for a in args {
|
|
c.arg(a);
|
|
}
|
|
c
|
|
};
|
|
cmd.env_remove("CLAUDECODE")
|
|
.env_remove("CLAUDE_CODE_ENTRYPOINT")
|
|
.creation_flags(CREATE_NO_WINDOW)
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null());
|
|
if cmd.spawn().is_ok() {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
fn spawn_wsl(distro: &str) -> bool {
|
|
let script = "if command -v claude >/dev/null 2>&1; then claude -p .; \
|
|
elif [ -x \"$HOME/.local/bin/claude\" ]; then \"$HOME/.local/bin/claude\" -p .; \
|
|
else exit 127; fi";
|
|
Command::new("wsl.exe")
|
|
.arg("-d")
|
|
.arg(distro)
|
|
.arg("--")
|
|
.arg("bash")
|
|
.arg("-lic")
|
|
.arg(script)
|
|
.env_remove("CLAUDECODE")
|
|
.env_remove("CLAUDE_CODE_ENTRYPOINT")
|
|
.creation_flags(CREATE_NO_WINDOW)
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.spawn()
|
|
.is_ok()
|
|
}
|