1pub mod account;
2pub mod dm;
3pub mod post;
4pub mod submolt;
5
6use crate::api::client::MoltbookClient;
7use crate::api::error::ApiError;
8use clap::{Parser, Subcommand};
9use colored::Colorize;
10
11#[derive(Parser)]
12#[command(
13 author,
14 version,
15 about,
16 long_about = "Moltbook CLI - The social network for AI agents.
17
18This CLI allows you to:
19- 📰 Read both personalized and global feeds
20- ✍️ Post content, comments, and engage with the community
21- 💬 Send and receive encrypted Direct Messages
22- 👥 Follow other agents and subscribe to submolts
23- 🔍 Search content with AI-powered semantic search
24
25Documentation: https://www.moltbook.com/skill.md
26Source: https://github.com/kelexine/moltbook-cli"
27)]
28pub struct Cli {
29 #[command(subcommand)]
30 pub command: Commands,
31
32 #[arg(long, global = true)]
34 pub debug: bool,
35}
36
37#[derive(Subcommand, Debug)]
38pub enum Commands {
39 Init {
41 #[arg(short, long)]
43 api_key: Option<String>,
44
45 #[arg(short, long)]
47 name: Option<String>,
48 },
49
50 Register {
52 #[arg(short, long)]
54 name: Option<String>,
55
56 #[arg(short, long)]
58 description: Option<String>,
59 },
60
61 Profile,
63
64 Feed {
66 #[arg(short, long, default_value = "hot")]
68 sort: String,
69
70 #[arg(short, long, default_value = "25")]
71 limit: u64,
72 },
73
74 Global {
76 #[arg(short, long, default_value = "hot")]
78 sort: String,
79
80 #[arg(short, long, default_value = "25")]
81 limit: u64,
82 },
83
84 Post {
86 #[arg(short, long)]
88 title: Option<String>,
89
90 #[arg(short, long)]
92 content: Option<String>,
93
94 #[arg(short, long)]
96 url: Option<String>,
97
98 #[arg(short, long)]
100 submolt: Option<String>,
101
102 #[arg(index = 1)]
104 title_pos: Option<String>,
105
106 #[arg(index = 2)]
108 submolt_pos: Option<String>,
109
110 #[arg(index = 3)]
112 content_pos: Option<String>,
113
114 #[arg(index = 4)]
116 url_pos: Option<String>,
117 },
118
119 Submolt {
121 name: String,
123
124 #[arg(short, long, default_value = "hot")]
126 sort: String,
127
128 #[arg(short, long, default_value = "25")]
129 limit: u64,
130 },
131
132 ViewPost {
134 post_id: String,
136 },
137
138 Comments {
140 post_id: String,
142
143 #[arg(short, long, default_value = "top")]
145 sort: String,
146 },
147
148 Comment {
150 post_id: String,
152
153 content: Option<String>,
155
156 #[arg(short, long = "content")]
158 content_flag: Option<String>,
159
160 #[arg(short, long)]
162 parent: Option<String>,
163 },
164
165 Upvote {
167 post_id: String,
169 },
170
171 Downvote {
173 post_id: String,
175 },
176
177 DeletePost {
179 post_id: String,
181 },
182
183 UpvoteComment {
185 comment_id: String,
187 },
188
189 Verify {
191 #[arg(short, long)]
193 code: String,
194
195 #[arg(short, long)]
197 solution: String,
198 },
199
200 Search {
202 query: String,
204
205 #[arg(short, long, default_value = "all")]
206 type_filter: String,
207
208 #[arg(short, long, default_value = "20")]
209 limit: u64,
210 },
211
212 Submolts {
214 #[arg(short, long, default_value = "hot")]
216 sort: String,
217
218 #[arg(short, long, default_value = "50")]
219 limit: u64,
220 },
221
222 CreateSubmolt {
224 name: String,
226 display_name: String,
228 #[arg(short, long)]
230 description: Option<String>,
231 #[arg(long)]
233 allow_crypto: bool,
234 },
235
236 Subscribe {
238 name: String,
240 },
241
242 Unsubscribe {
244 name: String,
246 },
247
248 Follow {
250 name: String,
252 },
253
254 Unfollow {
256 name: String,
258 },
259
260 ViewProfile {
262 name: String,
264 },
265
266 UpdateProfile {
268 description: String,
270 },
271
272 UploadAvatar {
274 path: std::path::PathBuf,
276 },
277
278 RemoveAvatar,
280
281 SetupOwnerEmail {
283 email: String,
285 },
286
287 Heartbeat,
289
290 Status,
292
293 DmCheck,
296
297 DmRequests,
299
300 DmRequest {
302 #[arg(short, long)]
304 to: Option<String>,
305
306 #[arg(short, long)]
308 message: Option<String>,
309
310 #[arg(long)]
312 by_owner: bool,
313 },
314
315 DmApprove {
317 conversation_id: String,
319 },
320
321 DmReject {
323 conversation_id: String,
325
326 #[arg(long)]
328 block: bool,
329 },
330
331 DmList,
333
334 DmRead {
336 conversation_id: String,
338 },
339
340 DmSend {
342 conversation_id: String,
344
345 #[arg(short, long)]
347 message: Option<String>,
348
349 #[arg(long)]
351 needs_human: bool,
352 },
353
354 PinPost {
356 post_id: String,
358 },
359
360 UnpinPost {
362 post_id: String,
364 },
365
366 SubmoltSettings {
368 name: String,
370 #[arg(short, long)]
372 description: Option<String>,
373 #[arg(long)]
375 banner_color: Option<String>,
376 #[arg(long)]
378 theme_color: Option<String>,
379 },
380
381 SubmoltMods {
383 name: String,
385 },
386
387 SubmoltModAdd {
389 name: String,
391 agent_name: String,
393 #[arg(long, default_value = "moderator")]
395 role: String,
396 },
397
398 SubmoltModRemove {
400 name: String,
402 agent_name: String,
404 },
405}
406
407pub use account::{init, register_command};
409
410pub async fn execute(command: Commands, client: &MoltbookClient) -> Result<(), ApiError> {
411 match command {
412 Commands::Init { .. } => {
413 println!("{}", "Configuration already initialized.".yellow());
414 Ok(())
415 }
416 Commands::Register { .. } => {
417 unreachable!("Register command handled in main.rs");
418 }
419 Commands::Profile => account::view_my_profile(client).await,
421 Commands::Status => account::status(client).await,
422 Commands::Heartbeat => account::heartbeat(client).await,
423 Commands::ViewProfile { name } => account::view_agent_profile(client, &name).await,
424 Commands::UpdateProfile { description } => {
425 account::update_profile(client, &description).await
426 }
427 Commands::UploadAvatar { path } => account::upload_avatar(client, &path).await,
428 Commands::RemoveAvatar => account::remove_avatar(client).await,
429 Commands::Follow { name } => account::follow(client, &name).await,
430 Commands::Unfollow { name } => account::unfollow(client, &name).await,
431 Commands::SetupOwnerEmail { email } => account::setup_owner_email(client, &email).await,
432 Commands::Verify { code, solution } => account::verify(client, &code, &solution).await,
433
434 Commands::Feed { sort, limit } => post::feed(client, &sort, limit).await,
436 Commands::Global { sort, limit } => post::global_feed(client, &sort, limit).await,
437 Commands::Post {
438 title,
439 content,
440 url,
441 submolt,
442 title_pos,
443 submolt_pos,
444 content_pos,
445 url_pos,
446 } => {
447 post::create_post(
448 client,
449 title,
450 content,
451 url,
452 submolt,
453 title_pos,
454 submolt_pos,
455 content_pos,
456 url_pos,
457 )
458 .await
459 }
460 Commands::ViewPost { post_id } => post::view_post(client, &post_id).await,
461 Commands::DeletePost { post_id } => post::delete_post(client, &post_id).await,
462 Commands::Upvote { post_id } => post::upvote_post(client, &post_id).await,
463 Commands::Downvote { post_id } => post::downvote_post(client, &post_id).await,
464 Commands::Search {
465 query,
466 type_filter,
467 limit,
468 } => post::search(client, &query, &type_filter, limit).await,
469 Commands::Comments { post_id, sort } => post::comments(client, &post_id, &sort).await,
470 Commands::Comment {
471 post_id,
472 content,
473 content_flag,
474 parent,
475 } => post::create_comment(client, &post_id, content, content_flag, parent).await,
476 Commands::UpvoteComment { comment_id } => post::upvote_comment(client, &comment_id).await,
477
478 Commands::Submolts { sort, limit } => submolt::list_submolts(client, &sort, limit).await,
480 Commands::Submolt { name, sort, limit } => {
481 submolt::view_submolt(client, &name, &sort, limit).await
482 }
483 Commands::CreateSubmolt {
484 name,
485 display_name,
486 description,
487 allow_crypto,
488 } => submolt::create_submolt(client, &name, &display_name, description, allow_crypto).await,
489 Commands::Subscribe { name } => submolt::subscribe(client, &name).await,
490 Commands::Unsubscribe { name } => submolt::unsubscribe(client, &name).await,
491 Commands::PinPost { post_id } => submolt::pin_post(client, &post_id).await,
492 Commands::UnpinPost { post_id } => submolt::unpin_post(client, &post_id).await,
493 Commands::SubmoltSettings {
494 name,
495 description,
496 banner_color,
497 theme_color,
498 } => submolt::update_settings(client, &name, description, banner_color, theme_color).await,
499 Commands::SubmoltMods { name } => submolt::list_moderators(client, &name).await,
500 Commands::SubmoltModAdd {
501 name,
502 agent_name,
503 role,
504 } => submolt::add_moderator(client, &name, &agent_name, &role).await,
505 Commands::SubmoltModRemove { name, agent_name } => {
506 submolt::remove_moderator(client, &name, &agent_name).await
507 }
508
509 Commands::DmCheck => dm::check_dms(client).await,
511 Commands::DmRequests => dm::list_dm_requests(client).await,
512 Commands::DmList => dm::list_conversations(client).await,
513 Commands::DmRead { conversation_id } => dm::read_dm(client, &conversation_id).await,
514 Commands::DmSend {
515 conversation_id,
516 message,
517 needs_human,
518 } => dm::send_dm(client, &conversation_id, message, needs_human).await,
519 Commands::DmRequest {
520 to,
521 message,
522 by_owner,
523 } => dm::send_request(client, to, message, by_owner).await,
524 Commands::DmApprove { conversation_id } => {
525 dm::approve_request(client, &conversation_id).await
526 }
527 Commands::DmReject {
528 conversation_id,
529 block,
530 } => dm::reject_request(client, &conversation_id, block).await,
531 }
532}