Skip to main content

moltbook_cli/cli/
account.rs

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}