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
288 Follow {
290 name: String,
292 },
293
294 Unfollow {
296 name: String,
298 },
299
300 ViewProfile {
302 name: String,
304 },
305
306 UpdateProfile {
308 description: String,
310 },
311
312 UploadAvatar {
314 path: std::path::PathBuf,
316 },
317
318 RemoveAvatar,
320
321 SetupOwnerEmail {
323 email: String,
325 },
326
327 Heartbeat,
329
330 Status,
332
333 DmCheck,
336
337 DmRequests,
339
340 DmRequest {
342 #[arg(short, long)]
344 to: Option<String>,
345
346 #[arg(short, long)]
348 message: Option<String>,
349
350 #[arg(long)]
352 by_owner: bool,
353 },
354
355 DmApprove {
357 conversation_id: String,
359 },
360
361 DmReject {
363 conversation_id: String,
365
366 #[arg(long)]
368 block: bool,
369 },
370
371 DmList,
373
374 DmRead {
376 conversation_id: String,
378 },
379
380 DmSend {
382 conversation_id: String,
384
385 #[arg(short, long)]
387 message: Option<String>,
388
389 #[arg(long)]
391 needs_human: bool,
392 },
393
394 PinPost {
396 post_id: String,
398 },
399
400 UnpinPost {
402 post_id: String,
404 },
405
406 SubmoltSettings {
408 name: String,
410 #[arg(short, long)]
412 description: Option<String>,
413 #[arg(long)]
415 banner_color: Option<String>,
416 #[arg(long)]
418 theme_color: Option<String>,
419 },
420
421 SubmoltMods {
423 name: String,
425 },
426
427 SubmoltModAdd {
429 name: String,
431 agent_name: String,
433 #[arg(long, default_value = "moderator")]
435 role: String,
436 },
437
438 SubmoltModRemove {
440 name: String,
442 agent_name: String,
444 },
445}
446
447pub use account::{init, register_command};
449
450pub async fn execute(command: Commands, client: &MoltbookClient) -> Result<(), ApiError> {
454 match command {
455 Commands::Init { .. } => {
456 println!("{}", "Configuration already initialized.".yellow());
457 Ok(())
458 }
459 Commands::Register { .. } => {
460 unreachable!("Register command handled in main.rs");
461 }
462 Commands::Profile => account::view_my_profile(client).await,
464 Commands::Status => account::status(client).await,
465 Commands::Heartbeat => account::heartbeat(client).await,
466 Commands::ViewProfile { name } => account::view_agent_profile(client, &name).await,
467 Commands::UpdateProfile { description } => {
468 account::update_profile(client, &description).await
469 }
470 Commands::UploadAvatar { path } => account::upload_avatar(client, &path).await,
471 Commands::RemoveAvatar => account::remove_avatar(client).await,
472 Commands::Follow { name } => account::follow(client, &name).await,
473 Commands::Unfollow { name } => account::unfollow(client, &name).await,
474 Commands::SetupOwnerEmail { email } => account::setup_owner_email(client, &email).await,
475 Commands::Verify { code, solution } => account::verify(client, &code, &solution).await,
476
477 Commands::Feed { sort, limit } => post::feed(client, &sort, limit).await,
479 Commands::Global { sort, limit } => post::global_feed(client, &sort, limit).await,
480 Commands::Post {
481 title,
482 content,
483 url,
484 submolt,
485 title_pos,
486 submolt_pos,
487 content_pos,
488 url_pos,
489 } => {
490 post::create_post(
491 client,
492 post::PostParams {
493 title,
494 content,
495 url,
496 submolt,
497 title_pos,
498 submolt_pos,
499 content_pos,
500 url_pos,
501 },
502 )
503 .await
504 }
505 Commands::ViewPost { post_id } => post::view_post(client, &post_id).await,
506 Commands::DeletePost { post_id } => post::delete_post(client, &post_id).await,
507 Commands::Upvote { post_id } => post::upvote_post(client, &post_id).await,
508 Commands::Downvote { post_id } => post::downvote_post(client, &post_id).await,
509 Commands::Search {
510 query,
511 type_filter,
512 limit,
513 } => post::search(client, &query, &type_filter, limit).await,
514 Commands::Comments { post_id, sort } => post::comments(client, &post_id, &sort).await,
515 Commands::Comment {
516 post_id,
517 content,
518 content_flag,
519 } => post::create_comment(client, &post_id, content, content_flag, None).await,
520 Commands::ReplyComment {
521 post_id,
522 parent_id,
523 content,
524 } => post::create_comment(client, &post_id, content, None, Some(parent_id)).await,
525 Commands::UpvoteComment { comment_id } => post::upvote_comment(client, &comment_id).await,
526
527 Commands::Submolts { sort, limit } => submolt::list_submolts(client, &sort, limit).await,
529 Commands::Submolt { name, sort, limit } => {
530 submolt::view_submolt(client, &name, &sort, limit).await
531 }
532 Commands::CreateSubmolt {
533 name,
534 display_name,
535 description,
536 allow_crypto,
537 } => submolt::create_submolt(client, &name, &display_name, description, allow_crypto).await,
538 Commands::Subscribe { name } => submolt::subscribe(client, &name).await,
539 Commands::Unsubscribe { name } => submolt::unsubscribe(client, &name).await,
540 Commands::SubmoltInfo { name } => submolt::submolt_info(client, &name).await,
541 Commands::UploadSubmoltAvatar { name, path } => submolt::upload_submolt_avatar(client, &name, &path).await,
542 Commands::UploadSubmoltBanner { name, path } => submolt::upload_submolt_banner(client, &name, &path).await,
543 Commands::PinPost { post_id } => submolt::pin_post(client, &post_id).await,
544 Commands::UnpinPost { post_id } => submolt::unpin_post(client, &post_id).await,
545 Commands::SubmoltSettings {
546 name,
547 description,
548 banner_color,
549 theme_color,
550 } => submolt::update_settings(client, &name, description, banner_color, theme_color).await,
551 Commands::SubmoltMods { name } => submolt::list_moderators(client, &name).await,
552 Commands::SubmoltModAdd {
553 name,
554 agent_name,
555 role,
556 } => submolt::add_moderator(client, &name, &agent_name, &role).await,
557 Commands::SubmoltModRemove { name, agent_name } => {
558 submolt::remove_moderator(client, &name, &agent_name).await
559 }
560
561 Commands::DmCheck => dm::check_dms(client).await,
563 Commands::DmRequests => dm::list_dm_requests(client).await,
564 Commands::DmList => dm::list_conversations(client).await,
565 Commands::DmRead { conversation_id } => dm::read_dm(client, &conversation_id).await,
566 Commands::DmSend {
567 conversation_id,
568 message,
569 needs_human,
570 } => dm::send_dm(client, &conversation_id, message, needs_human).await,
571 Commands::DmRequest {
572 to,
573 message,
574 by_owner,
575 } => dm::send_request(client, to, message, by_owner).await,
576 Commands::DmApprove { conversation_id } => {
577 dm::approve_request(client, &conversation_id).await
578 }
579 Commands::DmReject {
580 conversation_id,
581 block,
582 } => dm::reject_request(client, &conversation_id, block).await,
583 }
584}