Skip to content

Commit 01b6c6d

Browse files
committed
feat: add agent and namespace commands
1 parent 1c23364 commit 01b6c6d

File tree

9 files changed

+357
-14
lines changed

9 files changed

+357
-14
lines changed

cli/src/cli/agent.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use tabled::Table;
2+
use torus_client::client::TorusClient;
3+
4+
use crate::{
5+
cli::CliCtx,
6+
store::{get_account, get_key},
7+
};
8+
9+
#[derive(clap::Args)]
10+
#[command(arg_required_else_help = true)]
11+
pub struct AgentCliCommand {
12+
pub account: Option<String>,
13+
14+
#[command(subcommand)]
15+
pub sub_command: Option<AgentCliSubCommand>,
16+
}
17+
18+
#[derive(clap::Subcommand, Clone)]
19+
pub enum AgentCliSubCommand {
20+
Info {
21+
account: String,
22+
},
23+
Register {
24+
key: String,
25+
name: String,
26+
metadata: String,
27+
url: String,
28+
},
29+
}
30+
31+
#[derive(tabled::Tabled)]
32+
struct AgentInfo {
33+
name: String,
34+
metadata: String,
35+
url: String,
36+
last_update_block: u64,
37+
registration_block: u64,
38+
}
39+
40+
impl AgentInfo {
41+
fn new(
42+
name: Vec<u8>,
43+
metadata: Vec<u8>,
44+
url: Vec<u8>,
45+
last_update_block: u64,
46+
registration_block: u64,
47+
) -> Self {
48+
Self {
49+
name: String::from_utf8_lossy(&name[..]).to_string(),
50+
metadata: String::from_utf8_lossy(&metadata[..]).to_string(),
51+
url: String::from_utf8_lossy(&url[..]).to_string(),
52+
last_update_block,
53+
registration_block,
54+
}
55+
}
56+
}
57+
58+
pub async fn info(ctx: &CliCtx, account: String) -> anyhow::Result<()> {
59+
let account = get_account(&account)?;
60+
61+
println!("Fetching agent data...");
62+
63+
let agent_info = if ctx.is_testnet() {
64+
let client = TorusClient::for_testnet().await?;
65+
client
66+
.torus0()
67+
.storage()
68+
.agents_get(&account)
69+
.await?
70+
.map(|agent| {
71+
AgentInfo::new(
72+
agent.name.0,
73+
agent.metadata.0,
74+
agent.url.0,
75+
agent.last_update_block,
76+
agent.registration_block,
77+
)
78+
})
79+
} else {
80+
let client = TorusClient::for_mainnet().await?;
81+
client
82+
.torus0()
83+
.storage()
84+
.agents_get(&account)
85+
.await?
86+
.map(|agent| {
87+
AgentInfo::new(
88+
agent.name.0,
89+
agent.metadata.0,
90+
agent.url.0,
91+
agent.last_update_block,
92+
agent.registration_block,
93+
)
94+
})
95+
};
96+
97+
if let Some(agent_info) = agent_info {
98+
let table = Table::kv(std::iter::once(agent_info));
99+
println!("{table}");
100+
} else {
101+
println!("{account} is not an agent.");
102+
}
103+
104+
Ok(())
105+
}
106+
107+
pub async fn register(
108+
ctx: &CliCtx,
109+
key: String,
110+
name: String,
111+
metadata: String,
112+
url: String,
113+
) -> anyhow::Result<()> {
114+
let key = get_key(&key)?;
115+
let (_key, keypair) = ctx.decrypt(&key)?;
116+
117+
ctx.confirm(&format!(
118+
"register an agent with the IMMUTABLE name of {name}"
119+
))?;
120+
121+
println!("Registering agent...");
122+
123+
if ctx.is_testnet() {
124+
let client = TorusClient::for_testnet().await?;
125+
client
126+
.torus0()
127+
.calls()
128+
.register_agent_wait(
129+
name.as_bytes().to_vec(),
130+
url.as_bytes().to_vec(),
131+
metadata.as_bytes().to_vec(),
132+
keypair,
133+
)
134+
.await?;
135+
} else {
136+
let client = TorusClient::for_mainnet().await?;
137+
client
138+
.torus0()
139+
.calls()
140+
.register_agent_wait(
141+
name.as_bytes().to_vec(),
142+
url.as_bytes().to_vec(),
143+
metadata.as_bytes().to_vec(),
144+
keypair,
145+
)
146+
.await?;
147+
};
148+
149+
println!("Agent registered successfully!");
150+
151+
Ok(())
152+
}

cli/src/cli/balance.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub async fn transfer(
8282

8383
let target = AccountId32::from_str(&target)?;
8484

85+
ctx.confirm(&format!("transfer {amount} to {target}"))?;
86+
8587
println!("Transfering...");
8688

8789
if ctx.is_testnet() {

cli/src/cli/key.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ pub enum KeyCliSubCommand {
2424

2525
#[arg(long)]
2626
password: bool,
27+
28+
#[arg(long)]
29+
mnemonic: Option<String>,
2730
},
2831
Delete {
2932
name: String,
@@ -65,7 +68,12 @@ pub(super) fn list(_ctx: &CliCtx) -> anyhow::Result<()> {
6568
Ok(())
6669
}
6770

68-
pub(super) fn create(_ctx: &CliCtx, name: String, password: bool) -> anyhow::Result<()> {
71+
pub(super) fn create(
72+
_ctx: &CliCtx,
73+
name: String,
74+
password: bool,
75+
mnemonic: Option<String>,
76+
) -> anyhow::Result<()> {
6977
if key_exists(&name) {
7078
println!("A key with this name already exists.");
7179
return Ok(());
@@ -81,12 +89,17 @@ pub(super) fn create(_ctx: &CliCtx, name: String, password: bool) -> anyhow::Res
8189
None
8290
};
8391

84-
let mnemonic = Mnemonic::generate(12)?;
92+
let mnemonic = if let Some(mnemonic) = mnemonic {
93+
Mnemonic::parse(mnemonic)?
94+
} else {
95+
Mnemonic::generate(12)?
96+
};
97+
8598
let (keypair, seed) = generate_sr25519_keypair(&mnemonic, password.as_deref())?;
8699

87100
store_new_key(&name, &mnemonic, &seed, &keypair, password.as_deref())?;
88101

89-
print!(
102+
println!(
90103
"Created key `{}` with public key `{}`.",
91104
keypair.ss58_address(),
92105
keypair.hex_public_key()

cli/src/cli/mod.rs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,44 @@
11
use clap::Parser;
22

33
use balance::BalanceCliCommand;
4-
use inquire::Password;
4+
use inquire::{Password, PasswordDisplayMode};
55
use key::KeyCliCommand;
66

77
use crate::{
88
cli::{
9+
agent::{AgentCliCommand, AgentCliSubCommand},
910
key::KeyCliSubCommand,
11+
namespace::{NamespaceCliCommand, NamespaceCliSubCommand},
1012
stake::{StakeCliCommand, StakeCliSubCommand},
1113
},
1214
keypair::Keypair,
1315
store::{decrypt_key, Key},
1416
};
1517

18+
mod agent;
1619
mod balance;
1720
mod key;
21+
mod namespace;
1822
mod stake;
1923

2024
pub(super) async fn execute() -> anyhow::Result<()> {
2125
let cli = Cli::parse();
2226
let (ctx, command) = cli.extract_context();
2327

2428
match command {
29+
CliSubCommand::Agent(command) => match command.sub_command {
30+
Some(AgentCliSubCommand::Info { account }) => agent::info(&ctx, account).await?,
31+
Some(AgentCliSubCommand::Register {
32+
key,
33+
name,
34+
metadata,
35+
url,
36+
}) => agent::register(&ctx, key, name, metadata, url).await?,
37+
None if command.account.is_some() => {
38+
agent::info(&ctx, command.account.unwrap()).await?
39+
}
40+
_ => {}
41+
},
2542
CliSubCommand::Balance(command) => match command.sub_command {
2643
Some(balance::BalanceCliSubCommand::Check { key }) => balance::check(&ctx, key).await?,
2744
Some(balance::BalanceCliSubCommand::Transfer {
@@ -34,7 +51,11 @@ pub(super) async fn execute() -> anyhow::Result<()> {
3451
},
3552
CliSubCommand::Key(command) => match command.sub_command {
3653
KeyCliSubCommand::List => key::list(&ctx)?,
37-
KeyCliSubCommand::Create { name, password } => key::create(&ctx, name, password)?,
54+
KeyCliSubCommand::Create {
55+
name,
56+
password,
57+
mnemonic,
58+
} => key::create(&ctx, name, password, mnemonic)?,
3859
KeyCliSubCommand::Delete { name } => key::delete(&ctx, name)?,
3960
KeyCliSubCommand::Info { name } => key::info(&ctx, name)?,
4061
},
@@ -60,6 +81,18 @@ pub(super) async fn execute() -> anyhow::Result<()> {
6081
None if command.key.is_some() => stake::given(&ctx, command.key.unwrap()).await?,
6182
_ => {}
6283
},
84+
CliSubCommand::Namespace(command) => match command.sub_command {
85+
Some(NamespaceCliSubCommand::Info { account }) => {
86+
namespace::info(&ctx, account).await?
87+
}
88+
Some(NamespaceCliSubCommand::Register { key, path }) => {
89+
namespace::register(&ctx, key, path).await?
90+
}
91+
None if command.account.is_some() => {
92+
namespace::info(&ctx, command.account.unwrap()).await?
93+
}
94+
_ => {}
95+
},
6396
}
6497

6598
Ok(())
@@ -81,26 +114,31 @@ pub struct Cli {
81114
impl Cli {
82115
pub fn extract_context(self) -> (CliCtx, CliSubCommand) {
83116
let is_testnet = self.testnet;
117+
let yes = self.yes;
84118

85-
(CliCtx::new(is_testnet), self.command)
119+
(CliCtx::new(is_testnet, yes), self.command)
86120
}
87121
}
88122

89123
#[derive(clap::Subcommand)]
90124
pub enum CliSubCommand {
125+
Agent(AgentCliCommand),
91126
Balance(BalanceCliCommand),
92127
Key(KeyCliCommand),
128+
Namespace(NamespaceCliCommand),
93129
Stake(StakeCliCommand),
94130
}
95131

96132
pub struct CliCtx {
97133
testnet: bool,
134+
skip_confirmation: bool,
98135
}
99136

100137
impl CliCtx {
101-
pub fn new(is_testnet: bool) -> Self {
138+
pub fn new(is_testnet: bool, skip_confirmation: bool) -> Self {
102139
Self {
103140
testnet: is_testnet,
141+
skip_confirmation,
104142
}
105143
}
106144

@@ -123,4 +161,21 @@ impl CliCtx {
123161

124162
Ok((key.clone(), Keypair::from_key(key, Some(&password))?))
125163
}
164+
165+
pub fn confirm(&self, action: &str) -> anyhow::Result<()> {
166+
if self.skip_confirmation {
167+
return Ok(());
168+
}
169+
170+
let res = Password::new(&format!("Are you sure you want to \"{action}\"? [y/N] "))
171+
.without_confirmation()
172+
.with_display_mode(PasswordDisplayMode::Full)
173+
.prompt()?;
174+
175+
if !res.to_lowercase().starts_with("y") {
176+
return Err(anyhow::anyhow!("not confirmed"));
177+
}
178+
179+
Ok(())
180+
}
126181
}

0 commit comments

Comments
 (0)