Skip to content

Commit 2bba572

Browse files
committed
v0.1.64 - use Claude auth status for account identity
1 parent 9319806 commit 2bba572

6 files changed

Lines changed: 76 additions & 6 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tally",
3-
"version": "0.1.63",
3+
"version": "0.1.64",
44
"private": true,
55
"scripts": {
66
"tauri": "tauri",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tally"
3-
version = "0.1.63"
3+
version = "0.1.64"
44
edition = "2021"
55
rust-version = "1.77"
66

src-tauri/src/claude/oauth.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use chrono::Utc;
33
use serde::{Deserialize, Serialize};
44
use std::fs::File;
55
use std::path::{Path, PathBuf};
6+
use std::process::Command;
67

78
use super::api::{live_limits_from_usage_response, UsageResponse};
89
use super::cache::disk_cache_path;
@@ -17,6 +18,11 @@ const CLAUDE_OAUTH_TOKEN_URL: &str = "https://platform.claude.com/v1/oauth/token
1718
// Refresh proactively when within this many seconds of expiry so the next
1819
// HTTP call always carries a fresh token (no wasted 401 round-trip).
1920
const TOKEN_REFRESH_MARGIN_SECS: i64 = 60;
21+
#[cfg(windows)]
22+
const CREATE_NO_WINDOW: u32 = 0x08000000;
23+
24+
#[cfg(windows)]
25+
use std::os::windows::process::CommandExt;
2026

2127
#[derive(Debug, Deserialize)]
2228
struct ProfileResponse {
@@ -28,6 +34,16 @@ struct ProfileOrg {
2834
rate_limit_tier: Option<String>,
2935
}
3036

37+
#[derive(Debug, Deserialize)]
38+
#[serde(rename_all = "camelCase")]
39+
struct AuthStatus {
40+
logged_in: bool,
41+
email: Option<String>,
42+
org_id: Option<String>,
43+
subscription_type: Option<String>,
44+
auth_method: Option<String>,
45+
}
46+
3147
#[derive(Debug, Clone, Deserialize, Serialize)]
3248
struct CredentialsFile {
3349
#[serde(rename = "claudeAiOauth")]
@@ -167,6 +183,9 @@ pub(crate) fn read_oauth_token() -> Result<String> {
167183
}
168184

169185
pub(crate) fn active_account_identity() -> Option<AccountIdentity> {
186+
if let Some(identity) = active_auth_status_identity() {
187+
return Some(identity);
188+
}
170189
let (_, creds) = read_credentials().ok()?;
171190
crate::account::token_identity(
172191
"claude",
@@ -175,6 +194,57 @@ pub(crate) fn active_account_identity() -> Option<AccountIdentity> {
175194
)
176195
}
177196

197+
fn active_auth_status_identity() -> Option<AccountIdentity> {
198+
let mut cmd = Command::new("claude");
199+
#[cfg(windows)]
200+
cmd.creation_flags(CREATE_NO_WINDOW);
201+
let output = cmd.args(["auth", "status", "--json"]).output().ok()?;
202+
if !output.status.success() {
203+
return None;
204+
}
205+
let status: AuthStatus = serde_json::from_slice(&output.stdout).ok()?;
206+
if !status.logged_in {
207+
return None;
208+
}
209+
let mut parts = Vec::new();
210+
if let Some(org_id) = status
211+
.org_id
212+
.as_deref()
213+
.map(str::trim)
214+
.filter(|s| !s.is_empty())
215+
{
216+
parts.push(format!("org:{org_id}"));
217+
}
218+
if let Some(email) = status
219+
.email
220+
.as_deref()
221+
.map(str::trim)
222+
.filter(|s| !s.is_empty())
223+
{
224+
parts.push(format!("email:{}", email.to_ascii_lowercase()));
225+
}
226+
if parts.is_empty() {
227+
if let Some(method) = status
228+
.auth_method
229+
.as_deref()
230+
.map(str::trim)
231+
.filter(|s| !s.is_empty())
232+
{
233+
parts.push(format!("auth:{method}"));
234+
}
235+
}
236+
if parts.is_empty() {
237+
return None;
238+
}
239+
let raw = parts.join("|");
240+
let source = status
241+
.subscription_type
242+
.as_deref()
243+
.map(|sub| format!("claude-auth-status:{sub}"))
244+
.unwrap_or_else(|| "claude-auth-status".to_string());
245+
crate::account::explicit_identity("claude", &raw, &source)
246+
}
247+
178248
/// Lightweight availability probe: does a parseable credentials file with an
179249
/// access token exist on disk? Does NOT hit the network — refresh logic lives
180250
/// in `read_oauth_token` / `ensure_fresh_access_token` and only runs when we

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "TALLY - Ai Usage Monitor",
4-
"version": "0.1.63",
4+
"version": "0.1.64",
55
"identifier": "com.cjmedia.tally",
66
"build": {
77
"frontendDist": "../src"

0 commit comments

Comments
 (0)