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