use std::io::Write;
use std::path::PathBuf;
use clap::Args;
use pointbreak::keys::{KeyCustody, agent_has_key, list_keys};
use crate::cli::common::{count_label, discover_trust_set};
use crate::cli::{json, output};
#[derive(Debug, Args)]
pub(super) struct ListArgs {
#[arg(long, default_value = ".")]
repo: PathBuf,
#[command(flatten)]
format_args: output::FormatArgs,
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct KeyEntry {
name: String,
did_key: String,
default: bool,
enrolled: bool,
custody: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
agent_loaded: Option<bool>,
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct ListBody {
keys: Vec<KeyEntry>,
}
pub(super) fn run(
args: ListArgs,
stdout: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
let trust_set = discover_trust_set(&args.repo);
let keys = list_keys()?
.into_iter()
.map(|info| {
let did_key = info.signer_id().clone();
let (custody, agent_loaded) = match info.custody() {
KeyCustody::File => ("file", None),
KeyCustody::Agent => (
"agent",
did_key.ed25519_public_key().ok().and_then(agent_has_key),
),
};
KeyEntry {
default: info.name() == "default",
enrolled: trust_set.contains_signer(&did_key),
custody,
agent_loaded,
did_key: did_key.as_str().to_owned(),
name: info.name().to_owned(),
}
})
.collect();
let body = ListBody { keys };
let format = output::resolve_format(args.format_args.explicit(), output::OutputFormat::Json)?;
let text =
matches!(format.format, output::OutputFormat::Text).then(|| render_key_list_text(&body));
let document = json::DiagnosticDocument::new("pointbreak.key-list", body, vec![]);
output::write_document(stdout, format, &document, || {
text.expect("text lane resolves the digest source")
})
}
fn render_key_list_text(body: &ListBody) -> String {
if body.keys.is_empty() {
return "no keys".to_owned();
}
let mut lines = vec![format!("{}:", count_label(body.keys.len(), "key", "keys"))];
for key in &body.keys {
let mut line = format!(
" {} · {} · {} · {}",
key.name,
key.did_key,
if key.enrolled {
"enrolled"
} else {
"not enrolled"
},
key.custody,
);
match key.agent_loaded {
Some(true) => line.push_str(" (loaded)"),
Some(false) => line.push_str(" (not loaded)"),
None => {}
}
lines.push(line);
}
lines.join("\n")
}