radicle_cli/commands/
self.rs

1use std::ffi::OsString;
2
3use radicle::crypto::ssh;
4use radicle::Profile;
5
6use crate::terminal as term;
7use crate::terminal::args::{Args, Error, Help};
8use crate::terminal::Element as _;
9
10pub const HELP: Help = Help {
11    name: "self",
12    description: "Show information about your identity and device",
13    version: env!("RADICLE_VERSION"),
14    usage: r#"
15Usage
16
17    rad self [<option>...]
18
19Options
20
21    --did                Show your DID
22    --alias              Show your Node alias
23    --nid                Show your Node ID (NID)
24    --home               Show your Radicle home
25    --config             Show the location of your configuration file
26    --ssh-key            Show your public key in OpenSSH format
27    --ssh-fingerprint    Show your public key fingerprint in OpenSSH format
28    --help               Show help
29"#,
30};
31
32#[derive(Debug)]
33enum Show {
34    Alias,
35    NodeId,
36    Did,
37    Home,
38    Config,
39    SshKey,
40    SshFingerprint,
41    All,
42}
43
44#[derive(Debug)]
45pub struct Options {
46    show: Show,
47}
48
49impl Args for Options {
50    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
51        use lexopt::prelude::*;
52
53        let mut parser = lexopt::Parser::from_args(args);
54        let mut show: Option<Show> = None;
55
56        while let Some(arg) = parser.next()? {
57            match arg {
58                Long("alias") if show.is_none() => {
59                    show = Some(Show::Alias);
60                }
61                Long("nid") if show.is_none() => {
62                    show = Some(Show::NodeId);
63                }
64                Long("did") if show.is_none() => {
65                    show = Some(Show::Did);
66                }
67                Long("home") if show.is_none() => {
68                    show = Some(Show::Home);
69                }
70                Long("config") if show.is_none() => {
71                    show = Some(Show::Config);
72                }
73                Long("ssh-key") if show.is_none() => {
74                    show = Some(Show::SshKey);
75                }
76                Long("ssh-fingerprint") if show.is_none() => {
77                    show = Some(Show::SshFingerprint);
78                }
79                Long("help") | Short('h') => {
80                    return Err(Error::Help.into());
81                }
82                _ => return Err(anyhow::anyhow!(arg.unexpected())),
83            }
84        }
85
86        Ok((
87            Options {
88                show: show.unwrap_or(Show::All),
89            },
90            vec![],
91        ))
92    }
93}
94
95pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
96    let profile = ctx.profile()?;
97
98    match options.show {
99        Show::Alias => {
100            term::print(profile.config.alias());
101        }
102        Show::NodeId => {
103            term::print(profile.id());
104        }
105        Show::Did => {
106            term::print(profile.did());
107        }
108        Show::Home => {
109            term::print(profile.home().path().display());
110        }
111        Show::Config => {
112            term::print(profile.home.config().display());
113        }
114        Show::SshKey => {
115            term::print(ssh::fmt::key(profile.id()));
116        }
117        Show::SshFingerprint => {
118            term::print(ssh::fmt::fingerprint(profile.id()));
119        }
120        Show::All => all(&profile)?,
121    }
122
123    Ok(())
124}
125
126fn all(profile: &Profile) -> anyhow::Result<()> {
127    let mut table = term::Table::<2, term::Label>::default();
128
129    table.push([
130        term::format::style("Alias").into(),
131        term::format::primary(profile.config.alias()).into(),
132    ]);
133
134    let did = profile.did();
135    table.push([
136        term::format::style("DID").into(),
137        term::format::tertiary(did).into(),
138    ]);
139
140    let node_id = profile.id();
141    table.push([
142        term::format::style("└╴Node ID (NID)").into(),
143        term::format::tertiary(node_id).into(),
144    ]);
145
146    let ssh_agent = match ssh::agent::Agent::connect() {
147        Ok(c) => term::format::positive(format!(
148            "running ({})",
149            c.pid().map(|p| p.to_string()).unwrap_or(String::from("?"))
150        )),
151        Err(e) if e.is_not_running() => term::format::yellow(String::from("not running")),
152        Err(e) => term::format::negative(format!("error: {e}")),
153    };
154    table.push([
155        term::format::style("SSH").into(),
156        ssh_agent.to_string().into(),
157    ]);
158
159    let ssh_short = ssh::fmt::fingerprint(node_id);
160    table.push([
161        term::format::style("├╴Key (hash)").into(),
162        term::format::tertiary(ssh_short).into(),
163    ]);
164
165    let ssh_long = ssh::fmt::key(node_id);
166    table.push([
167        term::format::style("└╴Key (full)").into(),
168        term::format::tertiary(ssh_long).into(),
169    ]);
170
171    let home = profile.home();
172    table.push([
173        term::format::style("Home").into(),
174        term::format::tertiary(home.path().display()).into(),
175    ]);
176
177    let config_path = profile.home.config();
178    table.push([
179        term::format::style("├╴Config").into(),
180        term::format::tertiary(config_path.display()).into(),
181    ]);
182
183    let storage_path = profile.home.storage();
184    table.push([
185        term::format::style("├╴Storage").into(),
186        term::format::tertiary(storage_path.display()).into(),
187    ]);
188
189    let keys_path = profile.home.keys();
190    table.push([
191        term::format::style("├╴Keys").into(),
192        term::format::tertiary(keys_path.display()).into(),
193    ]);
194
195    table.push([
196        term::format::style("└╴Node").into(),
197        term::format::tertiary(profile.home.node().display()).into(),
198    ]);
199
200    table.print();
201
202    Ok(())
203}