mod actions;
mod control_server;
use actions::{amp, auth, daemon, serve};
use anyhow::Result;
use byokey_store::SqliteTokenStore;
use byokey_types::ProviderId;
use clap::{CommandFactory, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(
name = "byokey",
about = "BYOKEY — Bring Your Own Keys AI proxy",
version
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(clap::Args, Debug)]
struct ServerArgs {
#[arg(short, long, value_name = "FILE")]
config: Option<PathBuf>,
#[arg(short, long)]
port: Option<u16>,
#[arg(long)]
host: Option<String>,
#[arg(long, value_name = "PATH")]
db: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
log_file: Option<PathBuf>,
}
#[derive(clap::Args, Debug)]
struct DaemonArgs {
#[command(flatten)]
server: ServerArgs,
}
#[derive(clap::Args, Debug)]
struct StoreArgs {
#[arg(long, value_name = "PATH")]
db: Option<PathBuf>,
}
#[derive(Subcommand, Debug)]
enum Commands {
Serve {
#[command(flatten)]
server: ServerArgs,
},
Start {
#[command(flatten)]
daemon: DaemonArgs,
},
Stop,
Restart {
#[command(flatten)]
daemon: DaemonArgs,
},
Reload,
Service {
#[command(subcommand)]
action: daemon::ServiceAction,
},
Login {
provider: ProviderId,
#[arg(long, value_name = "NAME")]
account: Option<String>,
#[command(flatten)]
store: StoreArgs,
},
AddApiKey {
provider: ProviderId,
api_key: String,
#[arg(long, value_name = "NAME")]
account: Option<String>,
#[arg(long)]
label: Option<String>,
#[command(flatten)]
store: StoreArgs,
},
ImportClaudeCode {
#[arg(long, value_name = "NAME")]
account: Option<String>,
#[arg(long)]
label: Option<String>,
#[command(flatten)]
store: StoreArgs,
},
Logout {
provider: ProviderId,
#[arg(long, value_name = "NAME")]
account: Option<String>,
#[command(flatten)]
store: StoreArgs,
},
Status {
#[command(flatten)]
store: StoreArgs,
},
Accounts {
provider: ProviderId,
#[command(flatten)]
store: StoreArgs,
},
Switch {
provider: ProviderId,
account: String,
#[command(flatten)]
store: StoreArgs,
},
Amp {
#[command(subcommand)]
action: amp::AmpAction,
},
Openapi,
Completions {
shell: clap_complete::Shell,
},
}
async fn run(command: Commands) -> Result<()> {
match command {
Commands::Serve { server } => serve::cmd_serve(server).await,
Commands::Start { daemon } => daemon::cmd_start(daemon),
Commands::Stop => daemon::cmd_stop(),
Commands::Restart { daemon } => daemon::cmd_restart(daemon),
Commands::Reload => daemon::cmd_reload(),
Commands::Service { action } => daemon::cmd_service(action),
Commands::Login {
provider,
account,
store,
} => {
auth::AuthCmd::new(store.db)
.await?
.login(provider, account)
.await
}
Commands::AddApiKey {
provider,
api_key,
account,
label,
store,
} => {
let api_key = if api_key == "-" {
use std::io::Read as _;
let mut buf = String::new();
std::io::stdin().read_to_string(&mut buf)?;
buf.trim().to_string()
} else {
api_key
};
auth::AuthCmd::new(store.db)
.await?
.add_api_key(provider, api_key, account, label)
.await
}
Commands::ImportClaudeCode {
account,
label,
store,
} => {
auth::AuthCmd::new(store.db)
.await?
.import_claude_code(account, label)
.await
}
Commands::Logout {
provider,
account,
store,
} => {
auth::AuthCmd::new(store.db)
.await?
.logout(provider, account)
.await
}
Commands::Status { store } => auth::AuthCmd::new(store.db).await?.status().await,
Commands::Accounts { provider, store } => {
auth::AuthCmd::new(store.db).await?.accounts(provider).await
}
Commands::Switch {
provider,
account,
store,
} => {
auth::AuthCmd::new(store.db)
.await?
.switch(provider, account)
.await
}
Commands::Amp { action } => amp::cmd_amp(action),
Commands::Openapi => {
use utoipa::OpenApi as _;
let spec = byokey_proxy::ApiDoc::openapi()
.to_json()
.expect("OpenAPI spec serialization failed");
println!("{spec}");
Ok(())
}
Commands::Completions { shell } => {
clap_complete::generate(shell, &mut Cli::command(), "byokey", &mut std::io::stdout());
Ok(())
}
}
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
match run(cli.command).await {
Ok(()) => std::process::exit(0),
Err(e) => {
eprintln!("Error: {e:#}");
std::process::exit(1);
}
}
}
pub(crate) async fn open_store(db: Option<PathBuf>) -> Result<SqliteTokenStore> {
let path = match db {
Some(p) => p,
None => byokey_daemon::paths::db_path()?,
};
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let url = format!("sqlite://{}?mode=rwc", path.display());
SqliteTokenStore::new(&url)
.await
.map_err(|e| anyhow::anyhow!("database error: {e}"))
}