Skip to main content

systemprompt_cli/commands/core/services/
keygen.rs

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