systemprompt_cli/commands/core/services/
keygen.rs1use std::path::PathBuf;
7
8use anyhow::{Context, Result};
9use clap::Args;
10use serde::Serialize;
11use systemprompt_config::write_private_atomic;
12
13use super::signing::BundleSigningKey;
14use crate::shared::CommandOutput;
15
16#[derive(Debug, Clone, Args)]
17pub struct KeygenArgs {
18 #[arg(
19 long,
20 help = "Write the base64 seed to this file instead of printing it"
21 )]
22 pub out: Option<PathBuf>,
23}
24
25#[derive(Debug, Serialize)]
26pub struct KeygenOutcome {
27 pub public_key: String,
28 pub key_id: String,
29 pub seed: Option<String>,
30 pub seed_file: Option<String>,
31}
32
33pub fn execute(args: &KeygenArgs) -> Result<CommandOutput> {
34 let key = BundleSigningKey::generate();
35 let outcome = write_key(&key, args.out.as_deref())?;
36 Ok(CommandOutput::card_value("Bundle Signing Key", &outcome))
37}
38
39fn write_key(key: &BundleSigningKey, out: Option<&std::path::Path>) -> Result<KeygenOutcome> {
40 let mut outcome = KeygenOutcome {
41 public_key: key.public_key.clone(),
42 key_id: key.key_id.clone(),
43 seed: None,
44 seed_file: None,
45 };
46
47 match out {
48 Some(path) => {
49 if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
50 std::fs::create_dir_all(parent)
51 .with_context(|| format!("Failed to create {}", parent.display()))?;
52 }
53 write_private_atomic(path, key.seed_b64().as_bytes())
54 .with_context(|| format!("Failed to write {}", path.display()))?;
55 outcome.seed_file = Some(path.display().to_string());
56 },
57 None => outcome.seed = Some(key.seed_b64()),
58 }
59 Ok(outcome)
60}