use std::io::Write;
use std::sync::Arc;
use infinity_provider_models_dev::MultiProvider;
use infinity_provider_models_dev::oauth;
use infinity_provider_protocol::remote::serve_provider;
use tokio::io::AsyncBufReadExt as _;
use tracing_subscriber::EnvFilter;
type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[tokio::main]
async fn main() -> Result<(), BoxError> {
let mut rest = std::env::args().skip(1);
match rest.next().as_deref() {
Some("login") => login(rest.collect::<Vec<_>>().as_slice()).await,
Some(other) => {
Err(format!("unknown command: {other} (expected `login` or no arguments)").into())
}
None => serve().await,
}
}
async fn serve() -> Result<(), BoxError> {
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.with_ansi(false)
.init();
let provider = Arc::new(MultiProvider::from_env());
let (socket_path, server) = serve_provider(provider)?;
println!("{}", socket_path.display());
std::io::stdout().flush()?;
server.await;
Ok(())
}
async fn login(args: &[String]) -> Result<(), BoxError> {
let mut mode = oauth::AccountMode::Max;
let mut flags = args.iter();
while let Some(flag) = flags.next() {
match flag.as_str() {
"--mode" => {
let Some(value) = flags.next() else {
return Err("`login --mode` needs a value (max|console)".into());
};
mode = value.parse()?;
}
"--help" | "-h" => {
println!("usage: infinity-provider-models-dev login [--mode max|console]");
return Ok(());
}
other => {
return Err(format!("unknown login flag: {other}").into());
}
}
}
let auth = oauth::authorize(mode)?;
println!("Open this URL in your browser, sign in, and approve access:");
println!();
println!("{}", auth.url);
println!();
print!("Paste the redirected URL (or code) here: ");
std::io::stdout().flush()?;
let mut input = String::new();
tokio::io::BufReader::new(tokio::io::stdin())
.read_line(&mut input)
.await?;
let Some(callback) = oauth::parse_callback(&input) else {
return Err("could not find a code in that input; paste the full redirect URL".into());
};
if callback.state != auth.state {
return Err("state mismatch: this code is for a different sign-in attempt".into());
}
let client = reqwest::Client::new();
let issued = oauth::exchange_code(&client, &callback, &auth.verifier).await?;
let Some(issued) = issued else {
return Err("the endpoint rejected the code (expired or already used); try again".into());
};
let path = oauth::store_login(mode, &issued, oauth::now_ms())?;
println!();
println!("Signed in. Tokens stored in {}", path.display());
println!("Enable the subscription backend with: export INFINITY_CLAUDE_OAUTH=1");
Ok(())
}