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