use std::process::{ Command, Output };
pub const BIN : &str = env!( "CARGO_BIN_EXE_clp" );
#[ inline ]
#[ must_use ]
pub fn run_cs( args : &[ &str ] ) -> Output
{
Command::new( BIN )
.args( args )
.output()
.expect( "failed to execute claude_profile binary" )
}
#[ inline ]
#[ must_use ]
pub fn run_cs_with_env( args : &[ &str ], env : &[ ( &str, &str ) ] ) -> Output
{
let mut cmd = Command::new( BIN );
cmd.args( args );
cmd.env_remove( "PRO" );
for ( k, v ) in env { cmd.env( k, v ); }
cmd.output().expect( "failed to execute claude_profile binary" )
}
#[ inline ]
#[ must_use ]
pub fn run_cs_without_home( args : &[ &str ] ) -> Output
{
Command::new( BIN )
.args( args )
.env_remove( "HOME" )
.env_remove( "PRO" )
.output()
.expect( "failed to execute claude_profile binary" )
}
#[ inline ]
#[ must_use ]
pub fn stdout( o : &Output ) -> String { String::from_utf8_lossy( &o.stdout ).to_string() }
#[ inline ]
#[ must_use ]
pub fn stderr( o : &Output ) -> String { String::from_utf8_lossy( &o.stderr ).to_string() }
#[ inline ]
pub fn assert_exit( o : &Output, expected : i32 )
{
let actual = o.status.code().unwrap_or( -1 );
assert_eq!(
actual, expected,
"exit code: expected {expected}, got {actual}\nstdout: {}\nstderr: {}",
stdout( o ), stderr( o ),
);
}
#[ inline ]
#[ must_use ]
pub fn credential_json( sub_type : &str, tier : &str, expires_at_ms : u64 ) -> String
{
format!(
r#"{{"oauthAccount":{{"subscriptionType":"{sub_type}","rateLimitTier":"{tier}"}},"expiresAt":{expires_at_ms}}}"#,
)
}
#[ inline ]
pub fn write_credentials( home : &std::path::Path, sub_type : &str, tier : &str, expires_at_ms : u64 )
{
let claude_dir = home.join( ".claude" );
std::fs::create_dir_all( &claude_dir ).unwrap();
let creds = claude_dir.join( ".credentials.json" );
std::fs::write( creds, credential_json( sub_type, tier, expires_at_ms ) ).unwrap();
}
#[ inline ]
pub fn write_account( home : &std::path::Path, name : &str, sub_type : &str, tier : &str, expires_at_ms : u64, make_active : bool )
{
let credential_store = home.join( ".persistent" ).join( "claude" ).join( "credential" );
std::fs::create_dir_all( &credential_store ).unwrap();
let dest = credential_store.join( format!( "{name}.credentials.json" ) );
std::fs::write( dest, credential_json( sub_type, tier, expires_at_ms ) ).unwrap();
if make_active
{
std::fs::write( credential_store.join( claude_profile::account::active_marker_filename() ), name ).unwrap();
}
}
#[ inline ]
pub fn write_claude_json( home : &std::path::Path, email : &str )
{
let content = format!(
r#"{{"oauthAccount":{{"emailAddress":"{email}"}}}}"#,
);
std::fs::write( home.join( ".claude.json" ), content ).unwrap();
}
#[ inline ]
pub fn write_claude_json_full(
home : &std::path::Path,
email : &str,
display : &str,
role : &str,
billing : &str,
)
{
let content = format!(
r#"{{"oauthAccount":{{"emailAddress":"{email}","displayName":"{display}","organizationRole":"{role}","billingType":"{billing}"}}}}"#,
);
std::fs::write( home.join( ".claude.json" ), content ).unwrap();
}
#[ inline ]
pub fn write_claude_json_extended(
home : &std::path::Path,
tagged_id : &str,
uuid : &str,
capabilities : &[ &str ],
)
{
let caps = capabilities.iter()
.map( | c | format!( "\"{c}\"" ) )
.collect::< Vec< _ > >()
.join( "," );
let content = format!(
r#"{{"oauthAccount":{{"taggedId":"{tagged_id}","uuid":"{uuid}","capabilities":[{caps}]}}}}"#,
);
std::fs::write( home.join( ".claude.json" ), content ).unwrap();
}
#[ inline ]
pub fn write_settings_json( home : &std::path::Path, model : &str )
{
let claude_dir = home.join( ".claude" );
std::fs::create_dir_all( &claude_dir ).unwrap();
let content = format!( r#"{{"model":"{model}"}}"# );
std::fs::write( claude_dir.join( "settings.json" ), content ).unwrap();
}
fn merge_account_meta( home : &std::path::Path, name : &str, pairs : serde_json::Value )
{
let credential_store = home.join( ".persistent" ).join( "claude" ).join( "credential" );
std::fs::create_dir_all( &credential_store ).unwrap();
let meta_path = credential_store.join( format!( "{name}.json" ) );
let mut val : serde_json::Value = std::fs::read_to_string( &meta_path )
.ok()
.and_then( | s | serde_json::from_str( &s ).ok() )
.unwrap_or_else( || serde_json::json!( {} ) );
if let ( Some( dst ), Some( src ) ) = ( val.as_object_mut(), pairs.as_object() )
{
for ( k, v ) in src { dst.insert( k.clone(), v.clone() ); }
}
std::fs::write( meta_path, serde_json::to_string_pretty( &val ).map( | s | s + "\n" ).unwrap() ).unwrap();
}
#[ inline ]
pub fn write_account_claude_json(
home : &std::path::Path,
name : &str,
email : &str,
display : &str,
role : &str,
billing : &str,
)
{
merge_account_meta( home, name, serde_json::json!({
"oauthAccount": {
"emailAddress": email,
"displayName": display,
"organizationRole": role,
"billingType": billing,
}
}) );
}
#[ inline ]
pub fn write_account_claude_json_extended(
home : &std::path::Path,
name : &str,
tagged_id : &str,
uuid : &str,
capabilities : &[ &str ],
)
{
let caps : Vec< serde_json::Value > = capabilities.iter()
.map( | c | serde_json::Value::String( (*c).to_string() ) )
.collect();
merge_account_meta( home, name, serde_json::json!({
"oauthAccount": {
"taggedId": tagged_id,
"uuid": uuid,
"capabilities": caps,
}
}) );
}
#[ inline ]
pub fn write_account_settings_json( home : &std::path::Path, name : &str, model : &str )
{
merge_account_meta( home, name, serde_json::json!({ "model": model }) );
}
#[ inline ]
pub fn write_account_roles_json(
home : &std::path::Path,
name : &str,
org_uuid : &str,
org_name : &str,
org_role : &str,
)
{
merge_account_meta( home, name, serde_json::json!({
"organization_uuid": org_uuid,
"organization_name": org_name,
"organization_role": org_role,
"workspace_uuid": null,
"workspace_name": null,
}) );
}
#[ inline ]
pub fn write_account_profile_json(
home : &std::path::Path,
name : &str,
host : Option< &str >,
role : Option< &str >,
)
{
let mut pairs = serde_json::Map::new();
if let Some( h ) = host { pairs.insert( "host".into(), serde_json::Value::String( h.into() ) ); }
if let Some( r ) = role { pairs.insert( "role".into(), serde_json::Value::String( r.into() ) ); }
merge_account_meta( home, name, serde_json::Value::Object( pairs ) );
}
#[ inline ]
pub fn write_account_renewal_json( home : &std::path::Path, name : &str, renewal_at_iso : &str )
{
merge_account_meta( home, name, serde_json::json!({ "_renewal_at": renewal_at_iso }) );
}
#[ inline ]
pub fn write_account_quota_cache(
home : &std::path::Path,
name : &str,
h5_util : f64,
d7_util : f64,
d7_resets_at : Option< &str >,
)
{
let d7_resets : serde_json::Value = match d7_resets_at
{
Some( s ) => serde_json::Value::String( s.to_string() ),
None => serde_json::Value::Null,
};
merge_account_meta( home, name, serde_json::json!({
"cache": {
"fetched_at": "2026-01-01T00:00:00Z",
"status": "ok",
"five_hour": { "left_pct": h5_util },
"seven_day": { "left_pct": d7_util, "resets_at": d7_resets }
}
}) );
}
#[ inline ]
pub fn write_account_owner( home : &std::path::Path, name : &str, owner : &str )
{
merge_account_meta( home, name, serde_json::json!({ "owner": owner }) );
}
#[ inline ]
#[ must_use ]
pub fn account_exists( home : &std::path::Path, name : &str ) -> bool
{
home.join( ".persistent" ).join( "claude" ).join( "credential" )
.join( format!( "{name}.credentials.json" ) ).exists()
}
pub const FAR_FUTURE_MS : u64 = 9_999_999_999_000;
#[ inline ]
#[ must_use ]
pub fn near_future_ms() -> u64
{
use std::time::{ SystemTime, UNIX_EPOCH };
#[ allow( clippy::cast_possible_truncation ) ]
let now_ms = SystemTime::now().duration_since( UNIX_EPOCH ).unwrap().as_millis() as u64;
now_ms + 30 * 60 * 1000 }
pub const PAST_MS : u64 = 1_000_000_000;
#[ derive( Debug ) ]
pub struct DayEntry
{
pub date : &'static str,
pub models : Vec< ( &'static str, u64 ) >,
}
#[ inline ]
pub fn write_stats_cache(
home : &std::path::Path,
last_computed : Option< &str >,
daily : &[ DayEntry ],
)
{
let claude_dir = home.join( ".claude" );
std::fs::create_dir_all( &claude_dir ).unwrap();
let lcd = match last_computed
{
Some( d ) => format!( "\"lastComputedDate\":\"{d}\"," ),
None => String::new(),
};
let mut entries = Vec::new();
for day in daily
{
let mut model_pairs = Vec::new();
for ( model, tokens ) in &day.models
{
model_pairs.push( format!( "\"{model}\":{tokens}" ) );
}
entries.push( format!(
"{{\"date\":\"{}\",\"tokensByModel\":{{{}}}}}",
day.date,
model_pairs.join( "," ),
) );
}
let json = format!(
"{{{lcd}\"dailyModelTokens\":[{}]}}",
entries.join( "," ),
);
std::fs::write( claude_dir.join( "stats-cache.json" ), json ).unwrap();
}
#[ inline ]
pub fn write_stats_cache_raw( home : &std::path::Path, content : &str )
{
let claude_dir = home.join( ".claude" );
std::fs::create_dir_all( &claude_dir ).unwrap();
std::fs::write( claude_dir.join( "stats-cache.json" ), content ).unwrap();
}
#[ inline ]
#[ must_use ]
pub fn credential_json_with_token( token : &str ) -> String
{
format!(
r#"{{"oauthAccount":{{"subscriptionType":"max","rateLimitTier":"default_claude_max_20x"}},"expiresAt":{FAR_FUTURE_MS},"accessToken":"{token}"}}"#,
)
}
#[ inline ]
pub fn write_account_with_token(
home : &std::path::Path,
name : &str,
token : &str,
make_active : bool,
)
{
let credential_store = home.join( ".persistent" ).join( "claude" ).join( "credential" );
std::fs::create_dir_all( &credential_store ).unwrap();
let dest = credential_store.join( format!( "{name}.credentials.json" ) );
std::fs::write( dest, credential_json_with_token( token ) ).unwrap();
if make_active
{
std::fs::write( credential_store.join( claude_profile::account::active_marker_filename() ), name ).unwrap();
}
}
#[ inline ]
pub fn write_live_credentials_with_token( home : &std::path::Path, token : &str )
{
let claude_dir = home.join( ".claude" );
std::fs::create_dir_all( &claude_dir ).unwrap();
let content = format!(
r#"{{"accessToken":"{token}","oauthAccount":{{"subscriptionType":"max","rateLimitTier":"default"}},"expiresAt":{FAR_FUTURE_MS}}}"#,
);
std::fs::write( claude_dir.join( ".credentials.json" ), content ).unwrap();
}
#[ inline ]
#[ must_use ]
pub fn live_active_token() -> Option< String >
{
let home = std::env::var( "HOME" ).ok()?;
let content = std::fs::read_to_string(
std::path::Path::new( &home ).join( ".claude" ).join( ".credentials.json" ),
).ok()?;
claude_profile::account::parse_string_field( &content, "accessToken" )
}
#[ inline ]
#[ must_use ]
pub fn require_live_api( label : &str ) -> bool
{
let probe_path = std::env::temp_dir().join( ".lim_it_api_probe" );
if let Ok( meta ) = std::fs::metadata( &probe_path )
{
if meta.modified().ok()
.and_then( |m| m.elapsed().ok() )
.is_some_and( |age| age.as_secs() < 60 )
{
let cached = std::fs::read_to_string( &probe_path )
.is_ok_and( |s| s.trim() == "1" );
if !cached
{
eprintln!( "{label}: API rate-limited (cached probe) — skipping" );
}
return cached;
}
}
let token = live_active_token().unwrap_or_default();
let http_code = std::process::Command::new( "curl" )
.args([
"-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "5",
"-H", &format!( "Authorization: Bearer {token}" ),
"https://api.claude.ai/api/oauth/usage",
])
.output()
.ok()
.and_then( |o| String::from_utf8( o.stdout ).ok() )
.and_then( |s| s.trim().parse::< u16 >().ok() )
.unwrap_or( 0 );
let ok = http_code != 0 && http_code != 429;
let _ = std::fs::write( &probe_path, if ok { "1" } else { "0" } );
if !ok
{
eprintln!( "{label}: API rate-limited (HTTP {http_code}) — skipping" );
}
ok
}
#[ must_use ]
#[ inline ]
pub fn run_cs_bytes_for_secs( args : &[ &str ], env : &[ ( &str, &str ) ], secs : u64 ) -> Vec< u8 >
{
use std::process::Stdio;
use std::io::Read;
use std::sync::{ Arc, Mutex };
let mut cmd = std::process::Command::new( BIN );
cmd.args( args ).env_remove( "PRO" );
for ( k, v ) in env { cmd.env( k, v ); }
cmd.stdout( Stdio::piped() );
let mut child = cmd.spawn().expect( "failed to spawn binary" );
let mut stdout = child.stdout.take().unwrap();
let collected : Arc< Mutex< Vec< u8 > > > = Arc::new( Mutex::new( Vec::new() ) );
let collected2 = collected.clone();
let reader = std::thread::spawn( move ||
{
let mut buf = [ 0u8; 4096 ];
loop
{
match stdout.read( &mut buf )
{
Ok( 0 ) | Err( _ ) => break,
Ok( n ) => collected2.lock().unwrap().extend_from_slice( &buf[ ..n ] ),
}
}
} );
std::thread::sleep( core::time::Duration::from_secs( secs ) );
let _ = child.kill();
let _ = reader.join();
let _ = child.wait();
let guard = collected.lock().unwrap();
guard.clone()
}