use anyhow::{bail, Result};
use clap::{Args, Subcommand};
use serde_json::json;
use crate::commands::provision::{client_from_config, developer_session, resolve_app_id};
use crate::config::CliConfig;
use crate::output;
#[derive(Subcommand)]
pub enum KeysCommand {
List(ListArgs),
Create(CreateArgs),
}
#[derive(Args)]
pub struct ListArgs {
#[arg(long)]
pub app_id: Option<String>,
#[arg(long)]
pub json: bool,
}
#[derive(Args)]
pub struct CreateArgs {
pub alias: String,
#[arg(long, default_value = "all")]
pub product: String,
#[arg(long, default_value = "full_access")]
pub permissions: String,
#[arg(long)]
pub app_id: Option<String>,
#[arg(long)]
pub json: bool,
#[arg(long)]
pub set_default: bool,
}
pub async fn run(command: KeysCommand) -> Result<()> {
match command {
KeysCommand::List(list) => list_keys(list).await,
KeysCommand::Create(create) => create_key(create).await,
}
}
async fn list_keys(args: ListArgs) -> Result<()> {
let mut config = CliConfig::load()?;
let token = developer_session(&mut config, "keys").await?;
let app_id = resolve_app_id(&config, args.app_id.as_deref())?;
let client = client_from_config(&config).await?;
let keys = client.list_api_keys(&token, &app_id).await?;
if args.json {
output::json(&json!({ "appId": app_id, "keys": keys }))?;
return Ok(());
}
output::heading(&format!("api keys for {app_id}"));
if keys.is_empty() {
output::note("no keys — run `offline keys create \"CLI default\"`");
return Ok(());
}
for key in &keys {
let name = key.name.as_deref().unwrap_or("(unnamed)");
let product = key.product.as_deref().unwrap_or("?");
let status = key.status.as_deref().unwrap_or("?");
println!(" {name} product={product} status={status}");
}
Ok(())
}
async fn create_key(args: CreateArgs) -> Result<()> {
let mut config = CliConfig::load()?;
let token = developer_session(&mut config, "keys").await?;
let app_id = resolve_app_id(&config, args.app_id.as_deref())?;
let client = client_from_config(&config).await?;
let product = match args.product.as_str() {
"all" | "offline_id" | "mesh_sdk" | "proof_of_location" => args.product.clone(),
other => bail!(
"unsupported product `{other}` — use all, offline_id, mesh_sdk, or proof_of_location"
),
};
let key = client
.create_api_key(&token, &app_id, &args.alias, &product, &args.permissions)
.await?;
let api_key = key
.key
.clone()
.filter(|k| !k.is_empty())
.ok_or_else(|| anyhow::anyhow!("create API key response missing key value"))?;
if args.set_default {
config.app_keys.insert(app_id.clone(), api_key.clone());
config.api_key = Some(api_key.clone());
config.app_id = Some(app_id.clone());
config.save()?;
}
if args.json {
output::json(&json!({
"appId": app_id,
"key": key,
"setDefault": args.set_default,
}))?;
return Ok(());
}
output::success(&format!(
"created API key `{}` for application `{app_id}`",
args.alias
));
output::warn("Copy this key now — it will not be shown again:");
println!(" {api_key}");
if !args.set_default {
output::note("Run with --set-default to store it as the active CLI credential");
}
Ok(())
}