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
18pub async fn register_agent(
22 name_opt: Option<String>,
23 desc_opt: Option<String>,
24) -> Result<(String, String), ApiError> {
25 display::info("Registering New Agent");
26
27 let name = match name_opt {
28 Some(n) => n,
29 None => Input::with_theme(&ColorfulTheme::default())
30 .with_prompt("Agent Name")
31 .interact_text()
32 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
33 };
34
35 let description = match desc_opt {
36 Some(d) => d,
37 None => Input::with_theme(&ColorfulTheme::default())
38 .with_prompt("Description")
39 .allow_empty(true)
40 .interact_text()
41 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
42 };
43
44 let client = reqwest::Client::new();
45 let body = json!({
46 "name": name,
47 "description": description
48 });
49
50 display::info("Sending registration request...");
51 let response = client
52 .post("https://www.moltbook.com/api/v1/agents/register")
53 .header("Content-Type", "application/json")
54 .json(&body)
55 .send()
56 .await?;
57
58 if !response.status().is_success() {
59 let error_text = response.text().await?;
60 return Err(ApiError::MoltbookError(
61 "Registration failed".to_string(),
62 error_text,
63 ));
64 }
65
66 let reg_response: RegistrationResponse = response.json().await?;
67 let agent = reg_response.agent;
68
69 display::success("Registration Successful!");
70 println!("Details verified for: {}", agent.name.cyan());
71 println!("Claim URL: {}", agent.claim_url.yellow());
72 println!("Verification Code: {}", agent.verification_code.yellow());
73 println!(
74 "\n {} Give the Claim URL to your human to verify you!\n",
75 "IMPORTANT:".bold().red()
76 );
77
78 Ok((agent.api_key, agent.name))
79}
80
81pub async fn register_command(
83 name: Option<String>,
84 description: Option<String>,
85) -> Result<(), ApiError> {
86 let (api_key, agent_name) = register_agent(name, description).await?;
87
88 let config = Config {
89 api_key,
90 agent_name,
91 };
92
93 config.save()?;
94 display::success("Configuration saved successfully! 🦞");
95 Ok(())
96}
97
98pub async fn init(api_key_opt: Option<String>, name_opt: Option<String>) -> Result<(), ApiError> {
100 let (api_key, agent_name) = if let (Some(k), Some(n)) = (api_key_opt, name_opt) {
101 (k, n)
102 } else {
103 println!("{}", "Moltbook CLI Setup 🦞".green().bold());
104
105 let selections = &["Register new agent", "I already have an API key"];
106 let selection = Select::with_theme(&ColorfulTheme::default())
107 .with_prompt("Select an option")
108 .default(0)
109 .items(&selections[..])
110 .interact()
111 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
112
113 if selection == 0 {
114 register_agent(None, None).await?
115 } else {
116 display::info("Get your API key by registering at https://www.moltbook.com\n");
117
118 let key: String = Input::with_theme(&ColorfulTheme::default())
119 .with_prompt("API Key")
120 .interact_text()
121 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
122
123 let name: String = Input::with_theme(&ColorfulTheme::default())
124 .with_prompt("Agent Name")
125 .interact_text()
126 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
127
128 (key, name)
129 }
130 };
131
132 let config = Config {
133 api_key,
134 agent_name,
135 };
136
137 config.save()?;
138 display::success("Configuration saved successfully! 🦞");
139 Ok(())
140}
141
142pub async fn view_my_profile(client: &MoltbookClient) -> Result<(), ApiError> {
144 let response: serde_json::Value = client.get("/agents/me").await?;
145 let agent: Agent = if let Some(a) = response.get("agent") {
146 serde_json::from_value(a.clone())?
147 } else {
148 serde_json::from_value(response)?
149 };
150 display::display_profile(&agent, Some("Your Profile"));
151 Ok(())
152}
153
154pub async fn view_agent_profile(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
155 let response: serde_json::Value = client
156 .get(&format!("/agents/profile?name={}", name))
157 .await?;
158 let agent: Agent = if let Some(a) = response.get("agent") {
159 serde_json::from_value(a.clone())?
160 } else {
161 serde_json::from_value(response)?
162 };
163 display::display_profile(&agent, None);
164 Ok(())
165}
166
167pub async fn update_profile(client: &MoltbookClient, description: &str) -> Result<(), ApiError> {
168 let body = json!({ "description": description });
169 let result: serde_json::Value = client.patch("/agents/me", &body).await?;
170 if !crate::cli::verification::handle_verification(&result, "profile update")
171 && result["success"].as_bool().unwrap_or(false)
172 {
173 display::success("Profile updated!");
174 }
175 Ok(())
176}
177
178pub async fn upload_avatar(
179 client: &MoltbookClient,
180 path: &std::path::Path,
181) -> Result<(), ApiError> {
182 let result: serde_json::Value = client
183 .post_file("/agents/me/avatar", path.to_path_buf())
184 .await?;
185 if !crate::cli::verification::handle_verification(&result, "avatar upload")
186 && result["success"].as_bool().unwrap_or(false)
187 {
188 display::success("Avatar uploaded successfully! 🦞");
189 }
190 Ok(())
191}
192
193pub async fn remove_avatar(client: &MoltbookClient) -> Result<(), ApiError> {
194 let result: serde_json::Value = client.delete("/agents/me/avatar").await?;
195 if !crate::cli::verification::handle_verification(&result, "avatar removal")
196 && result["success"].as_bool().unwrap_or(false)
197 {
198 display::success("Avatar removed");
199 }
200 Ok(())
201}
202
203pub async fn status(client: &MoltbookClient) -> Result<(), ApiError> {
204 let response: StatusResponse = client.get("/agents/status").await?;
205 display::display_status(&response);
206 Ok(())
207}
208
209pub async fn heartbeat(client: &MoltbookClient) -> Result<(), ApiError> {
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 !crate::cli::verification::handle_verification(&result, "follow action")
246 && result["success"].as_bool().unwrap_or(false)
247 {
248 display::success(&format!("Now following {}", resolved_name));
249 } else if !result["success"].as_bool().unwrap_or(false) {
250 let error = result["error"].as_str().unwrap_or("Unknown error");
251 display::error(&format!("Failed to follow {}: {}", resolved_name, error));
252 }
253 } else {
254 display::error(&format!("Molty '{}' not found", name));
255 }
256 Ok(())
257}
258
259pub async fn unfollow(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
260 let response: serde_json::Value = client
261 .get(&format!("/agents/profile?name={}", name))
262 .await?;
263 if let Some(agent) = response.get("agent") {
264 let resolved_name = agent["name"].as_str().ok_or(ApiError::MoltbookError(
265 "Agent name not found in profile".to_string(),
266 "".to_string(),
267 ))?;
268 let result: serde_json::Value = client
269 .delete(&format!("/agents/{}/follow", resolved_name))
270 .await?;
271 if !crate::cli::verification::handle_verification(&result, "unfollow action")
272 && result["success"].as_bool().unwrap_or(false)
273 {
274 display::success(&format!("Unfollowed {}", resolved_name));
275 } else if !result["success"].as_bool().unwrap_or(false) {
276 let error = result["error"].as_str().unwrap_or("Unknown error");
277 display::error(&format!("Failed to unfollow {}: {}", resolved_name, error));
278 }
279 } else {
280 display::error(&format!("Molty '{}' not found", name));
281 }
282 Ok(())
283}
284
285pub async fn setup_owner_email(client: &MoltbookClient, email: &str) -> Result<(), ApiError> {
286 let body = json!({ "email": email });
287 let result: serde_json::Value = client.post("/agents/me/setup-owner-email", &body).await?;
288 if !crate::cli::verification::handle_verification(&result, "email setup")
289 && result["success"].as_bool().unwrap_or(false)
290 {
291 display::success("Owner email set! Check your inbox to verify dashboard access.");
292 }
293 Ok(())
294}
295
296pub async fn verify(client: &MoltbookClient, code: &str, solution: &str) -> Result<(), ApiError> {
297 let body = json!({
298 "verification_code": code,
299 "answer": solution
300 });
301 let result = client.post::<serde_json::Value>("/verify", &body).await;
302
303 match result {
304 Ok(res) => {
305 if res["success"].as_bool().unwrap_or(false) {
306 display::success("Verification Successful!");
307
308 if let Some(post) = res.get("post") {
309 if let Ok(p) = serde_json::from_value::<crate::api::types::Post>(post.clone()) {
310 display::display_post(&p, None);
311 }
312 } else if let Some(comment) = res.get("comment") {
313 display::display_comment(comment, 0);
314 } else if let Some(agent) = res.get("agent")
315 && let Ok(a) = serde_json::from_value::<crate::api::types::Agent>(agent.clone())
316 {
317 display::display_profile(&a, Some("Verified Agent Profile"));
318 }
319
320 if let Some(id) = res["id"].as_str() {
321 println!("{} {}", "ID:".bright_white().bold(), id.dimmed());
322 }
323
324 if let Some(msg) = res["message"].as_str() {
325 display::info(msg);
326 }
327
328 if let Some(suggestion) = res["suggestion"].as_str() {
329 println!("💡 {}", suggestion.dimmed());
330 }
331 } else {
332 let error = res["error"].as_str().unwrap_or("Unknown error");
333 display::error(&format!("Verification Failed: {}", error));
334 }
335 }
336 Err(ApiError::MoltbookError(msg, _hint)) if msg == "Already answered" => {
337 display::info("Already Verified");
338 println!("{}", "This challenge has already been completed.".blue());
339 }
340 Err(e) => {
341 display::error(&format!("Verification Failed: {}", e));
342 }
343 }
344 Ok(())
345}