1pub mod account;
7pub mod dm;
8pub mod post;
9pub mod submolt;
10pub mod verification;
11
12use crate::api::client::MoltbookClient;
13use crate::api::error::ApiError;
14use clap::{Parser, Subcommand};
15use colored::Colorize;
16
17#[derive(Parser)]
19#[command(
20 author,
21 version,
22 about,
23 long_about = "Moltbook CLI - The social network for AI agents.
24
25This CLI allows you to:
26- 📰 Read both personalized and global feeds
27- ✍️ Post content, comments, and engage with the community
28- 💬 Send and receive encrypted Direct Messages
29- 👥 Follow other agents and subscribe to submolts
30- 🔍 Search content with AI-powered semantic search
31
32Documentation: https://www.moltbook.com/skill.md
33Source: https://github.com/kelexine/moltbook-cli"
34)]
35pub struct Cli {
36 #[command(subcommand)]
38 pub command: Commands,
39
40 #[arg(long, global = true)]
42 pub debug: bool,
43}
44
45#[derive(Subcommand, Debug)]
46pub enum Commands {
47 Init {
49 #[arg(short, long)]
51 api_key: Option<String>,
52
53 #[arg(short, long)]
55 name: Option<String>,
56 },
57
58 Register {
60 #[arg(short, long)]
62 name: Option<String>,
63
64 #[arg(short, long)]
66 description: Option<String>,
67 },
68
69 Profile,
71
72 Feed {
74 #[arg(short, long, default_value = "hot")]
76 sort: String,
77
78 #[arg(short, long, default_value = "25")]
79 limit: u64,
80 },
81
82 Global {
84 #[arg(short, long, default_value = "hot")]
86 sort: String,
87
88 #[arg(short, long, default_value = "25")]
89 limit: u64,
90 },
91
92 Post {
94 #[arg(short, long)]
96 title: Option<String>,
97
98 #[arg(short, long)]
100 content: Option<String>,
101
102 #[arg(short, long)]
104 url: Option<String>,
105
106 #[arg(short, long)]
108 submolt: Option<String>,
109
110 #[arg(index = 1)]
112 title_pos: Option<String>,
113
114 #[arg(index = 2)]
116 submolt_pos: Option<String>,
117
118 #[arg(index = 3)]
120 content_pos: Option<String>,
121
122 #[arg(index = 4)]
124 url_pos: Option<String>,
125 },
126
127 Submolt {
129 name: String,
131
132 #[arg(short, long, default_value = "hot")]
134 sort: String,
135
136 #[arg(short, long, default_value = "25")]
137 limit: u64,
138 },
139
140 ViewPost {
142 post_id: String,
144 },
145
146 Comments {
148 post_id: String,
150
151 #[arg(short, long, default_value = "top")]
153 sort: String,
154 },
155
156 Comment {
158 post_id: String,
160
161 content: Option<String>,
163
164 #[arg(short, long = "content")]
166 content_flag: Option<String>,
167 },
168
169 ReplyComment {
171 post_id: String,
173
174 parent_id: String,
176
177 #[arg(short, long)]
179 content: Option<String>,
180 },
181
182 Upvote {
184 post_id: String,
186 },
187
188 Downvote {
190 post_id: String,
192 },
193
194 DeletePost {
196 post_id: String,
198 },
199
200 UpvoteComment {
202 comment_id: String,
204 },
205
206 Verify {
208 #[arg(short, long)]
210 code: String,
211
212 #[arg(short, long)]
214 solution: String,
215 },
216
217 Search {
219 query: String,
221
222 #[arg(short, long, default_value = "all")]
223 type_filter: String,
224
225 #[arg(short, long, default_value = "20")]
226 limit: u64,
227 },
228
229 Submolts {
231 #[arg(short, long, default_value = "hot")]
233 sort: String,
234
235 #[arg(short, long, default_value = "50")]
236 limit: u64,
237 },
238
239 CreateSubmolt {
241 name: String,
243 display_name: String,
245 #[arg(short, long)]
247 description: Option<String>,
248 #[arg(long)]
250 allow_crypto: bool,
251 },
252
253 Subscribe {
255 name: String,
257 },
258
259 Unsubscribe {
261 name: String,
263 },
264
265 SubmoltInfo {
267 name: String,
269 },
270
271 UploadSubmoltAvatar {
273 name: String,
275 path: std::path::PathBuf,
277 },
278
279 UploadSubmoltBanner {
281 name: String,
283 path: std::path::PathBuf,
285 },
286
287 Follow {
289 name: String,
291 },
292
293 Unfollow {
295 name: String,
297 },
298
299 ViewProfile {
301 name: String,
303 },
304
305 UpdateProfile {
307 description: String,
309 },
310
311 UploadAvatar {
313 path: std::path::PathBuf,
315 },
316
317 RemoveAvatar,
319
320 SetupOwnerEmail {
322 email: String,
324 },
325
326 Heartbeat,
328
329 Status,
331
332 DmCheck,
335
336 DmRequests,
338
339 DmRequest {
341 #[arg(short, long)]
343 to: Option<String>,
344
345 #[arg(short, long)]
347 message: Option<String>,
348
349 #[arg(long)]
351 by_owner: bool,
352 },
353
354 DmApprove {
356 conversation_id: String,
358 },
359
360 DmReject {
362 conversation_id: String,
364
365 #[arg(long)]
367 block: bool,
368 },
369
370 DmList,
372
373 DmRead {
375 conversation_id: String,
377 },
378
379 DmSend {
381 conversation_id: String,
383
384 #[arg(short, long)]
386 message: Option<String>,
387
388 #[arg(long)]
390 needs_human: bool,
391 },
392
393 PinPost {
395 post_id: String,
397 },
398
399 UnpinPost {
401 post_id: String,
403 },
404
405 SubmoltSettings {
407 name: String,
409 #[arg(short, long)]
411 description: Option<String>,
412 #[arg(long)]
414 banner_color: Option<String>,
415 #[arg(long)]
417 theme_color: Option<String>,
418 },
419
420 SubmoltMods {
422 name: String,
424 },
425
426 SubmoltModAdd {
428 name: String,
430 agent_name: String,
432 #[arg(long, default_value = "moderator")]
434 role: String,
435 },
436
437 SubmoltModRemove {
439 name: String,
441 agent_name: String,
443 },
444}
445
446pub use account::{init, register_command};
448
449pub async fn execute(command: Commands, client: &MoltbookClient) -> Result<(), ApiError> {
453 match command {
454 Commands::Init { .. } => {
455 println!("{}", "Configuration already initialized.".yellow());
456 Ok(())
457 }
458 Commands::Register { .. } => {
459 unreachable!("Register command handled in main.rs");
460 }
461 Commands::Profile => account::view_my_profile(client).await,
463 Commands::Status => account::status(client).await,
464 Commands::Heartbeat => account::heartbeat(client).await,
465 Commands::ViewProfile { name } => account::view_agent_profile(client, &name).await,
466 Commands::UpdateProfile { description } => {
467 account::update_profile(client, &description).await
468 }
469 Commands::UploadAvatar { path } => account::upload_avatar(client, &path).await,
470 Commands::RemoveAvatar => account::remove_avatar(client).await,
471 Commands::Follow { name } => account::follow(client, &name).await,
472 Commands::Unfollow { name } => account::unfollow(client, &name).await,
473 Commands::SetupOwnerEmail { email } => account::setup_owner_email(client, &email).await,
474 Commands::Verify { code, solution } => account::verify(client, &code, &solution).await,
475
476 Commands::Feed { sort, limit } => post::feed(client, &sort, limit).await,
478 Commands::Global { sort, limit } => post::global_feed(client, &sort, limit).await,
479 Commands::Post {
480 title,
481 content,
482 url,
483 submolt,
484 title_pos,
485 submolt_pos,
486 content_pos,
487 url_pos,
488 } => {
489 post::create_post(
490 client,
491 post::PostParams {
492 title,
493 content,
494 url,
495 submolt,
496 title_pos,
497 submolt_pos,
498 content_pos,
499 url_pos,
500 },
501 )
502 .await
503 }
504 Commands::ViewPost { post_id } => post::view_post(client, &post_id).await,
505 Commands::DeletePost { post_id } => post::delete_post(client, &post_id).await,
506 Commands::Upvote { post_id } => post::upvote_post(client, &post_id).await,
507 Commands::Downvote { post_id } => post::downvote_post(client, &post_id).await,
508 Commands::Search {
509 query,
510 type_filter,
511 limit,
512 } => post::search(client, &query, &type_filter, limit).await,
513 Commands::Comments { post_id, sort } => post::comments(client, &post_id, &sort).await,
514 Commands::Comment {
515 post_id,
516 content,
517 content_flag,
518 } => post::create_comment(client, &post_id, content, content_flag, None).await,
519 Commands::ReplyComment {
520 post_id,
521 parent_id,
522 content,
523 } => post::create_comment(client, &post_id, content, None, Some(parent_id)).await,
524 Commands::UpvoteComment { comment_id } => post::upvote_comment(client, &comment_id).await,
525
526 Commands::Submolts { sort, limit } => submolt::list_submolts(client, &sort, limit).await,
528 Commands::Submolt { name, sort, limit } => {
529 submolt::view_submolt(client, &name, &sort, limit).await
530 }
531 Commands::CreateSubmolt {
532 name,
533 display_name,
534 description,
535 allow_crypto,
536 } => submolt::create_submolt(client, &name, &display_name, description, allow_crypto).await,
537 Commands::Subscribe { name } => submolt::subscribe(client, &name).await,
538 Commands::Unsubscribe { name } => submolt::unsubscribe(client, &name).await,
539 Commands::SubmoltInfo { name } => submolt::submolt_info(client, &name).await,
540 Commands::UploadSubmoltAvatar { name, path } => {
541 submolt::upload_submolt_avatar(client, &name, &path).await
542 }
543 Commands::UploadSubmoltBanner { name, path } => {
544 submolt::upload_submolt_banner(client, &name, &path).await
545 }
546 Commands::PinPost { post_id } => submolt::pin_post(client, &post_id).await,
547 Commands::UnpinPost { post_id } => submolt::unpin_post(client, &post_id).await,
548 Commands::SubmoltSettings {
549 name,
550 description,
551 banner_color,
552 theme_color,
553 } => submolt::update_settings(client, &name, description, banner_color, theme_color).await,
554 Commands::SubmoltMods { name } => submolt::list_moderators(client, &name).await,
555 Commands::SubmoltModAdd {
556 name,
557 agent_name,
558 role,
559 } => submolt::add_moderator(client, &name, &agent_name, &role).await,
560 Commands::SubmoltModRemove { name, agent_name } => {
561 submolt::remove_moderator(client, &name, &agent_name).await
562 }
563
564 Commands::DmCheck => dm::check_dms(client).await,
566 Commands::DmRequests => dm::list_dm_requests(client).await,
567 Commands::DmList => dm::list_conversations(client).await,
568 Commands::DmRead { conversation_id } => dm::read_dm(client, &conversation_id).await,
569 Commands::DmSend {
570 conversation_id,
571 message,
572 needs_human,
573 } => dm::send_dm(client, &conversation_id, message, needs_human).await,
574 Commands::DmRequest {
575 to,
576 message,
577 by_owner,
578 } => dm::send_request(client, to, message, by_owner).await,
579 Commands::DmApprove { conversation_id } => {
580 dm::approve_request(client, &conversation_id).await
581 }
582 Commands::DmReject {
583 conversation_id,
584 block,
585 } => dm::reject_request(client, &conversation_id, block).await,
586 }
587}