radicle_cli/commands/
self.rs

1use std::ffi::OsString;
2
3use radicle::crypto::ssh;
4use radicle::node::Handle as _;
5use radicle::{Node, Profile};
6
7use crate::terminal as term;
8use crate::terminal::args::{Args, Error, Help};
9use crate::terminal::Element as _;
10
11pub const HELP: Help = Help {
12    name: "self",
13    description: "Show information about your identity and device",
14    version: env!("RADICLE_VERSION"),
15    usage: r#"
16Usage
17
18    rad self [<option>...]
19
20Options
21
22    --did                Show your DID
23    --alias              Show your Node alias
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                _ => anyhow::bail!(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            crate::warning::deprecated("rad self --nid", "rad node status --only nid");
104            term::print(
105                Node::new(profile.socket())
106                    .nid()
107                    .ok()
108                    .unwrap_or_else(|| *profile.id()),
109            );
110        }
111        Show::Did => {
112            term::print(profile.did());
113        }
114        Show::Home => {
115            term::print(profile.home().path().display());
116        }
117        Show::Config => {
118            term::print(profile.home.config().display());
119        }
120        Show::SshKey => {
121            term::print(ssh::fmt::key(profile.id()));
122        }
123        Show::SshFingerprint => {
124            term::print(ssh::fmt::fingerprint(profile.id()));
125        }
126        Show::All => all(&profile)?,
127    }
128
129    Ok(())
130}
131
132fn all(profile: &Profile) -> anyhow::Result<()> {
133    let mut table = term::Table::<2, term::Label>::default();
134
135    table.push([
136        term::format::style("Alias").into(),
137        term::format::primary(profile.config.alias()).into(),
138    ]);
139
140    let did = profile.did();
141    table.push([
142        term::format::style("DID").into(),
143        term::format::tertiary(did).into(),
144    ]);
145
146    let socket = profile.socket();
147    let node = if Node::new(&socket).is_running() {
148        term::format::positive(format!("running ({})", socket.display()))
149    } else {
150        term::format::negative("not running".to_string())
151    };
152    table.push([term::format::style("Node").into(), node.to_string().into()]);
153
154    let ssh_agent = match ssh::agent::Agent::connect() {
155        Ok(c) => term::format::positive(format!(
156            "running ({})",
157            c.path()
158                .map(|p| p.display().to_string())
159                .unwrap_or(String::from("?"))
160        )),
161        Err(e) if e.is_not_running() => term::format::yellow(String::from("not running")),
162        Err(e) => term::format::negative(format!("error: {e}")),
163    };
164    table.push([
165        term::format::style("SSH").into(),
166        ssh_agent.to_string().into(),
167    ]);
168
169    let id = profile.id();
170    let ssh_short = ssh::fmt::fingerprint(id);
171    table.push([
172        term::format::style("├╴Key (hash)").into(),
173        term::format::tertiary(ssh_short).into(),
174    ]);
175
176    let ssh_long = ssh::fmt::key(id);
177    table.push([
178        term::format::style("└╴Key (full)").into(),
179        term::format::tertiary(ssh_long).into(),
180    ]);
181
182    let home = profile.home();
183    table.push([
184        term::format::style("Home").into(),
185        term::format::tertiary(home.path().display()).into(),
186    ]);
187
188    let config_path = profile.home.config();
189    table.push([
190        term::format::style("├╴Config").into(),
191        term::format::tertiary(config_path.display()).into(),
192    ]);
193
194    let storage_path = profile.home.storage();
195    table.push([
196        term::format::style("├╴Storage").into(),
197        term::format::tertiary(storage_path.display()).into(),
198    ]);
199
200    let keys_path = profile.home.keys();
201    table.push([
202        term::format::style("├╴Keys").into(),
203        term::format::tertiary(keys_path.display()).into(),
204    ]);
205
206    table.push([
207        term::format::style("└╴Node").into(),
208        term::format::tertiary(profile.home.node().display()).into(),
209    ]);
210
211    table.print();
212
213    Ok(())
214}