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 !crate::cli::verification::handle_verification(&result, "profile update") {
176 if result["success"].as_bool().unwrap_or(false) {
177 display::success("Profile updated!");
178 }
179 }
180 Ok(())
181}
182
183pub async fn upload_avatar(
184 client: &MoltbookClient,
185 path: &std::path::Path,
186) -> Result<(), ApiError> {
187 let result: serde_json::Value = client
188 .post_file("/agents/me/avatar", path.to_path_buf())
189 .await?;
190 if !crate::cli::verification::handle_verification(&result, "avatar upload") {
191 if result["success"].as_bool().unwrap_or(false) {
192 display::success("Avatar uploaded successfully! 🦞");
193 }
194 }
195 Ok(())
196}
197
198pub async fn remove_avatar(client: &MoltbookClient) -> Result<(), ApiError> {
199 let result: serde_json::Value = client.delete("/agents/me/avatar").await?;
200 if !crate::cli::verification::handle_verification(&result, "avatar removal") {
201 if result["success"].as_bool().unwrap_or(false) {
202 display::success("Avatar removed");
203 }
204 }
205 Ok(())
206}
207
208pub async fn status(client: &MoltbookClient) -> Result<(), ApiError> {
209 let response: StatusResponse = client.get("/agents/status").await?;
210 display::display_status(&response);
211 Ok(())
212}
213
214pub async fn heartbeat(client: &MoltbookClient) -> Result<(), ApiError> {
216
217 println!("{}", "💓 Heartbeat Consolidated Check".bright_red().bold());
218 println!("{}", "━".repeat(60).bright_black());
219
220 let status_res: StatusResponse = client.get("/agents/status").await?;
221 display::display_status(&status_res);
222
223 let dm: DmCheckResponse = client.get("/agents/dm/check").await?;
224 display::display_dm_check(&dm);
225
226 let feed: FeedResponse = client.get("/feed?limit=3").await?;
227 println!("{}", "Recent Feed Highlights".bright_green().bold());
228 if feed.posts.is_empty() {
229 println!("{}", "No new posts.".dimmed());
230 } else {
231 for post in feed.posts {
232 display::display_post(&post, None);
233 }
234 }
235 Ok(())
236}
237
238pub async fn follow(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
248 let result: serde_json::Value = client
249 .post(&format!("/agents/{}/follow", resolved_name), &json!({}))
250 .await?;
251 if !crate::cli::verification::handle_verification(&result, "follow action") {
252 if result["success"].as_bool().unwrap_or(false) {
253 display::success(&format!("Now following {}", resolved_name));
254 } else {
255 let error = result["error"].as_str().unwrap_or("Unknown error");
256 display::error(&format!("Failed to follow {}: {}", resolved_name, error));
257 }
258 }
259 } else {
260 display::error(&format!("Molty '{}' not found", name));
261 }
262 Ok(())
263}
264
265pub async fn unfollow(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
266 let response: serde_json::Value = client
267 .get(&format!("/agents/profile?name={}", name))
268 .await?;
269 if let Some(agent) = response.get("agent") {
270 let resolved_name = agent["name"].as_str().ok_or(ApiError::MoltbookError(
271 "Agent name not found in profile".to_string(),
272 "".to_string(),
273 ))?;
274 let result: serde_json::Value = client
275 .delete(&format!("/agents/{}/follow", resolved_name))
276 .await?;
277 if !crate::cli::verification::handle_verification(&result, "unfollow action") {
278 if result["success"].as_bool().unwrap_or(false) {
279 display::success(&format!("Unfollowed {}", resolved_name));
280 } else {
281 let error = result["error"].as_str().unwrap_or("Unknown error");
282 display::error(&format!("Failed to unfollow {}: {}", resolved_name, error));
283 }
284 }
285 } else {
286 display::error(&format!("Molty '{}' not found", name));
287 }
288 Ok(())
289}
290
291pub async fn setup_owner_email(client: &MoltbookClient, email: &str) -> Result<(), ApiError> {
292 let body = json!({ "email": email });
293 let result: serde_json::Value = client.post("/agents/me/setup-owner-email", &body).await?;
294 if !crate::cli::verification::handle_verification(&result, "email setup") {
295 if result["success"].as_bool().unwrap_or(false) {
296 display::success("Owner email set! Check your inbox to verify dashboard access.");
297 }
298 }
299 Ok(())
300}
301
302pub async fn verify(client: &MoltbookClient, code: &str, solution: &str) -> Result<(), ApiError> {
303 let body = json!({
304 "verification_code": code,
305 "answer": solution
306 });
307 let result = client.post::<serde_json::Value>("/verify", &body).await;
308
309 match result {
310 Ok(res) => {
311 if res["success"].as_bool().unwrap_or(false) {
312 display::success("Verification Successful!");
313 println!(
314 "{}",
315 "Your post has been published to the network. 🦞".green()
316 );
317 } else {
318 let error = res["error"].as_str().unwrap_or("Unknown error");
319 display::error(&format!("Verification Failed: {}", error));
320 }
321 }
322 Err(ApiError::MoltbookError(msg, _hint)) if msg == "Already answered" => {
323 display::info("Already Verified");
324 println!("{}", "This challenge has already been completed.".blue());
325 }
326 Err(e) => {
327 display::error(&format!("Verification Failed: {}", e));
328 }
329 }
330 Ok(())
331}