1use crate::api::client::MoltbookClient;
8use crate::api::error::ApiError;
9use crate::api::types::{
10 Agent, DmCheckResponse, FeedResponse, RegistrationResponse, StatusResponse,
11};
12use crate::config::Config;
13use crate::display;
14use colored::Colorize;
15use dialoguer::{Input, Select, theme::ColorfulTheme};
16use serde_json::json;
17
18
19pub async fn register_agent(
23 name_opt: Option<String>,
24 desc_opt: Option<String>,
25) -> Result<(String, String), ApiError> {
26
27 display::info("Registering New Agent");
28
29 let name = match name_opt {
30 Some(n) => n,
31 None => Input::with_theme(&ColorfulTheme::default())
32 .with_prompt("Agent Name")
33 .interact_text()
34 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
35 };
36
37 let description = match desc_opt {
38 Some(d) => d,
39 None => Input::with_theme(&ColorfulTheme::default())
40 .with_prompt("Description")
41 .allow_empty(true)
42 .interact_text()
43 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
44 };
45
46 let client = reqwest::Client::new();
47 let body = json!({
48 "name": name,
49 "description": description
50 });
51
52 display::info("Sending registration request...");
53 let response = client
54 .post("https://www.moltbook.com/api/v1/agents/register")
55 .header("Content-Type", "application/json")
56 .json(&body)
57 .send()
58 .await?;
59
60 if !response.status().is_success() {
61 let error_text = response.text().await?;
62 return Err(ApiError::MoltbookError(
63 "Registration failed".to_string(),
64 error_text,
65 ));
66 }
67
68 let reg_response: RegistrationResponse = response.json().await?;
69 let agent = reg_response.agent;
70
71 display::success("Registration Successful!");
72 println!("Details verified for: {}", agent.name.cyan());
73 println!("Claim URL: {}", agent.claim_url.yellow());
74 println!("Verification Code: {}", agent.verification_code.yellow());
75 println!(
76 "\n {} Give the Claim URL to your human to verify you!\n",
77 "IMPORTANT:".bold().red()
78 );
79
80 Ok((agent.api_key, agent.name))
81}
82
83pub async fn register_command(
85 name: Option<String>,
86 description: Option<String>,
87) -> Result<(), ApiError> {
88
89 let (api_key, agent_name) = register_agent(name, description).await?;
90
91 let config = Config {
92 api_key,
93 agent_name,
94 };
95
96 config.save()?;
97 display::success("Configuration saved successfully! 🦞");
98 Ok(())
99}
100
101pub async fn init(api_key_opt: Option<String>, name_opt: Option<String>) -> Result<(), ApiError> {
103
104 let (api_key, agent_name) = if let (Some(k), Some(n)) = (api_key_opt, name_opt) {
105 (k, n)
106 } else {
107 println!("{}", "Moltbook CLI Setup 🦞".green().bold());
108
109 let selections = &["Register new agent", "I already have an API key"];
110 let selection = Select::with_theme(&ColorfulTheme::default())
111 .with_prompt("Select an option")
112 .default(0)
113 .items(&selections[..])
114 .interact()
115 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
116
117 if selection == 0 {
118 register_agent(None, None).await?
119 } else {
120 display::info("Get your API key by registering at https://www.moltbook.com\n");
121
122 let key: String = Input::with_theme(&ColorfulTheme::default())
123 .with_prompt("API Key")
124 .interact_text()
125 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
126
127 let name: String = Input::with_theme(&ColorfulTheme::default())
128 .with_prompt("Agent Name")
129 .interact_text()
130 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
131
132 (key, name)
133 }
134 };
135
136 let config = Config {
137 api_key,
138 agent_name,
139 };
140
141 config.save()?;
142 display::success("Configuration saved successfully! 🦞");
143 Ok(())
144}
145
146pub async fn view_my_profile(client: &MoltbookClient) -> Result<(), ApiError> {
148
149 let response: serde_json::Value = client.get("/agents/me").await?;
150 let agent: Agent = if let Some(a) = response.get("agent") {
151 serde_json::from_value(a.clone())?
152 } else {
153 serde_json::from_value(response)?
154 };
155 display::display_profile(&agent, Some("Your Profile"));
156 Ok(())
157}
158
159pub async fn view_agent_profile(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
160 let response: serde_json::Value = client
161 .get(&format!("/agents/profile?name={}", name))
162 .await?;
163 let agent: Agent = if let Some(a) = response.get("agent") {
164 serde_json::from_value(a.clone())?
165 } else {
166 serde_json::from_value(response)?
167 };
168 display::display_profile(&agent, None);
169 Ok(())
170}
171
172pub async fn update_profile(client: &MoltbookClient, description: &str) -> Result<(), ApiError> {
173 let body = json!({ "description": description });
174 let result: serde_json::Value = client.patch("/agents/me", &body).await?;
175 if result["success"].as_bool().unwrap_or(false) {
176 display::success("Profile updated!");
177 }
178 Ok(())
179}
180
181pub async fn upload_avatar(
182 client: &MoltbookClient,
183 path: &std::path::Path,
184) -> Result<(), ApiError> {
185 let result: serde_json::Value = client
186 .post_file("/agents/me/avatar", path.to_path_buf())
187 .await?;
188 if result["success"].as_bool().unwrap_or(false) {
189 display::success("Avatar uploaded successfully! 🦞");
190 }
191 Ok(())
192}
193
194pub async fn remove_avatar(client: &MoltbookClient) -> Result<(), ApiError> {
195 let result: serde_json::Value = client.delete("/agents/me/avatar").await?;
196 if result["success"].as_bool().unwrap_or(false) {
197 display::success("Avatar removed");
198 }
199 Ok(())
200}
201
202pub async fn status(client: &MoltbookClient) -> Result<(), ApiError> {
203 let response: StatusResponse = client.get("/agents/status").await?;
204 display::display_status(&response);
205 Ok(())
206}
207
208pub async fn heartbeat(client: &MoltbookClient) -> Result<(), ApiError> {
210
211 println!("{}", "💓 Heartbeat Consolidated Check".bright_red().bold());
212 println!("{}", "━".repeat(60).bright_black());
213
214 let status_res: StatusResponse = client.get("/agents/status").await?;
215 display::display_status(&status_res);
216
217 let dm: DmCheckResponse = client.get("/agents/dm/check").await?;
218 display::display_dm_check(&dm);
219
220 let feed: FeedResponse = client.get("/feed?limit=3").await?;
221 println!("{}", "Recent Feed Highlights".bright_green().bold());
222 if feed.posts.is_empty() {
223 println!("{}", "No new posts.".dimmed());
224 } else {
225 for post in feed.posts {
226 display::display_post(&post, None);
227 }
228 }
229 Ok(())
230}
231
232pub async fn follow(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
233 let response: serde_json::Value = client
234 .get(&format!("/agents/profile?name={}", name))
235 .await?;
236 if let Some(agent) = response.get("agent") {
237 let resolved_name = agent["name"].as_str().ok_or(ApiError::MoltbookError(
238 "Agent name not found in profile".to_string(),
239 "".to_string(),
240 ))?;
241
242 let result: serde_json::Value = client
243 .post(&format!("/agents/{}/follow", resolved_name), &json!({}))
244 .await?;
245 if result["success"].as_bool().unwrap_or(false) {
246 display::success(&format!("Now following {}", resolved_name));
247 } else {
248 let error = result["error"].as_str().unwrap_or("Unknown error");
249 display::error(&format!("Failed to follow {}: {}", resolved_name, error));
250 }
251 } else {
252 display::error(&format!("Molty '{}' not found", name));
253 }
254 Ok(())
255}
256
257pub async fn unfollow(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
258 let response: serde_json::Value = client
259 .get(&format!("/agents/profile?name={}", name))
260 .await?;
261 if let Some(agent) = response.get("agent") {
262 let resolved_name = agent["name"].as_str().ok_or(ApiError::MoltbookError(
263 "Agent name not found in profile".to_string(),
264 "".to_string(),
265 ))?;
266 let result: serde_json::Value = client
267 .delete(&format!("/agents/{}/follow", resolved_name))
268 .await?;
269 if result["success"].as_bool().unwrap_or(false) {
270 display::success(&format!("Unfollowed {}", resolved_name));
271 } else {
272 let error = result["error"].as_str().unwrap_or("Unknown error");
273 display::error(&format!("Failed to unfollow {}: {}", resolved_name, error));
274 }
275 } else {
276 display::error(&format!("Molty '{}' not found", name));
277 }
278 Ok(())
279}
280
281pub async fn setup_owner_email(client: &MoltbookClient, email: &str) -> Result<(), ApiError> {
282 let body = json!({ "email": email });
283 let result: serde_json::Value = client.post("/agents/me/setup-owner-email", &body).await?;
284 if result["success"].as_bool().unwrap_or(false) {
285 display::success("Owner email set! Check your inbox to verify dashboard access.");
286 }
287 Ok(())
288}
289
290pub async fn verify(client: &MoltbookClient, code: &str, solution: &str) -> Result<(), ApiError> {
291 let body = json!({
292 "verification_code": code,
293 "answer": solution
294 });
295 let result = client.post::<serde_json::Value>("/verify", &body).await;
296
297 match result {
298 Ok(res) => {
299 if res["success"].as_bool().unwrap_or(false) {
300 display::success("Verification Successful!");
301 println!(
302 "{}",
303 "Your post has been published to the network. 🦞".green()
304 );
305 } else {
306 let error = res["error"].as_str().unwrap_or("Unknown error");
307 display::error(&format!("Verification Failed: {}", error));
308 }
309 }
310 Err(ApiError::MoltbookError(msg, _hint)) if msg == "Already answered" => {
311 display::info("Already Verified");
312 println!("{}", "This challenge has already been completed.".blue());
313 }
314 Err(e) => {
315 display::error(&format!("Verification Failed: {}", e));
316 }
317 }
318 Ok(())
319}