Skip to main content

cyphr_cli/commands/
init.rs

1//! Identity initialization command.
2
3use cyphr::Principal;
4use cyphr_storage::export_commits;
5
6use super::common::{generate_key, load_key_from_keystore, parse_store};
7use crate::keystore::{JsonKeyStore, KeyStore};
8use crate::{Cli, Error, OutputFormat};
9
10/// Run the init command.
11pub fn run(
12    cli: &Cli,
13    algo: &str,
14    key_tmb: Option<&str>,
15    keys_tmb: Option<&[String]>,
16) -> crate::Result<()> {
17    let mut keystore = JsonKeyStore::open(&cli.keystore)?;
18
19    // Determine which genesis path to use
20    let principal = match (key_tmb, keys_tmb) {
21        // Explicit genesis with multiple keys
22        (None, Some(tmbs)) if !tmbs.is_empty() => {
23            let keys = tmbs
24                .iter()
25                .map(|tmb| load_key_from_keystore(&keystore, tmb))
26                .collect::<Result<Vec<_>, _>>()?;
27            Principal::explicit(keys)?
28        },
29
30        // Implicit genesis with existing key
31        (Some(tmb), None) => {
32            let key = load_key_from_keystore(&keystore, tmb)?;
33            Principal::implicit(key)?
34        },
35
36        // Generate new key and use implicit genesis
37        (None, None) => {
38            let (tmb_str, stored, key) = generate_key(algo, None)?;
39            keystore.store(&tmb_str, stored)?;
40            keystore.save()?;
41            Principal::implicit(key)?
42        },
43
44        // Conflicting options
45        (Some(_), Some(_)) => {
46            return Err(Error::InvalidArgument(
47                "cannot specify both --key and --keys".into(),
48            ));
49        },
50
51        // Empty explicit list
52        (None, Some(_)) => {
53            return Err(Error::InvalidArgument(
54                "--keys requires at least one thumbprint".into(),
55            ));
56        },
57    };
58
59    // Get identity string for output: PR if available, else PS
60    let identity_str = {
61        use coz::base64ct::{Base64UrlUnpadded, Encoding};
62        if let Some(pr) = principal.pg() {
63            pr.as_multihash()
64                .first_variant()
65                .map(Base64UrlUnpadded::encode_string)
66                .map_err(|e| Error::Storage(format!("PR empty: {e}")))?
67        } else {
68            principal
69                .pr()
70                .as_multihash()
71                .first_variant()
72                .map(Base64UrlUnpadded::encode_string)
73                .map_err(|e| Error::Storage(format!("PS empty: {e}")))?
74        }
75    };
76
77    // Store the identity (only if PR is set — L3+ explicit genesis)
78    let store = parse_store(&cli.store)?;
79    if let Some(pr_ref) = principal.pg() {
80        let commits = export_commits(&principal)?;
81        for commit in &commits {
82            store.append_commit(pr_ref, commit)?;
83        }
84    }
85
86    // Output result
87    match cli.output {
88        OutputFormat::Json => {
89            let keys: Vec<_> = principal.active_keys().map(|k| k.tmb.to_b64()).collect();
90            let output = serde_json::json!({
91                "pr": identity_str,
92                "keys": keys,
93            });
94            println!("{}", serde_json::to_string_pretty(&output)?);
95        },
96        OutputFormat::Table => {
97            println!("Created identity");
98            println!("  pr: {identity_str}");
99            println!("  keys:");
100            for key in principal.active_keys() {
101                let tag_str = key.tag.as_deref().unwrap_or("-");
102                println!("    {} ({}) [{}]", key.tmb.to_b64(), key.alg, tag_str);
103            }
104            println!("  stored: {}", cli.store);
105        },
106    }
107
108    Ok(())
109}