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 Follow {
267 name: String,
269 },
270
271 Unfollow {
273 name: String,
275 },
276
277 ViewProfile {
279 name: String,
281 },
282
283 UpdateProfile {
285 description: String,
287 },
288
289 UploadAvatar {
291 path: std::path::PathBuf,
293 },
294
295 RemoveAvatar,
297
298 SetupOwnerEmail {
300 email: String,
302 },
303
304 Heartbeat,
306
307 Status,
309
310 DmCheck,
313
314 DmRequests,
316
317 DmRequest {
319 #[arg(short, long)]
321 to: Option<String>,
322
323 #[arg(short, long)]
325 message: Option<String>,
326
327 #[arg(long)]
329 by_owner: bool,
330 },
331
332 DmApprove {
334 conversation_id: String,
336 },
337
338 DmReject {
340 conversation_id: String,
342
343 #[arg(long)]
345 block: bool,
346 },
347
348 DmList,
350
351 DmRead {
353 conversation_id: String,
355 },
356
357 DmSend {
359 conversation_id: String,
361
362 #[arg(short, long)]
364 message: Option<String>,
365
366 #[arg(long)]
368 needs_human: bool,
369 },
370
371 PinPost {
373 post_id: String,
375 },
376
377 UnpinPost {
379 post_id: String,
381 },
382
383 SubmoltSettings {
385 name: String,
387 #[arg(short, long)]
389 description: Option<String>,
390 #[arg(long)]
392 banner_color: Option<String>,
393 #[arg(long)]
395 theme_color: Option<String>,
396 },
397
398 SubmoltMods {
400 name: String,
402 },
403
404 SubmoltModAdd {
406 name: String,
408 agent_name: String,
410 #[arg(long, default_value = "moderator")]
412 role: String,
413 },
414
415 SubmoltModRemove {
417 name: String,
419 agent_name: String,
421 },
422}
423
424pub use account::{init, register_command};
426
427pub async fn execute(command: Commands, client: &MoltbookClient) -> Result<(), ApiError> {
431 match command {
432 Commands::Init { .. } => {
433 println!("{}", "Configuration already initialized.".yellow());
434 Ok(())
435 }
436 Commands::Register { .. } => {
437 unreachable!("Register command handled in main.rs");
438 }
439 Commands::Profile => account::view_my_profile(client).await,
441 Commands::Status => account::status(client).await,
442 Commands::Heartbeat => account::heartbeat(client).await,
443 Commands::ViewProfile { name } => account::view_agent_profile(client, &name).await,
444 Commands::UpdateProfile { description } => {
445 account::update_profile(client, &description).await
446 }
447 Commands::UploadAvatar { path } => account::upload_avatar(client, &path).await,
448 Commands::RemoveAvatar => account::remove_avatar(client).await,
449 Commands::Follow { name } => account::follow(client, &name).await,
450 Commands::Unfollow { name } => account::unfollow(client, &name).await,
451 Commands::SetupOwnerEmail { email } => account::setup_owner_email(client, &email).await,
452 Commands::Verify { code, solution } => account::verify(client, &code, &solution).await,
453
454 Commands::Feed { sort, limit } => post::feed(client, &sort, limit).await,
456 Commands::Global { sort, limit } => post::global_feed(client, &sort, limit).await,
457 Commands::Post {
458 title,
459 content,
460 url,
461 submolt,
462 title_pos,
463 submolt_pos,
464 content_pos,
465 url_pos,
466 } => {
467 post::create_post(
468 client,
469 post::PostParams {
470 title,
471 content,
472 url,
473 submolt,
474 title_pos,
475 submolt_pos,
476 content_pos,
477 url_pos,
478 },
479 )
480 .await
481 }
482 Commands::ViewPost { post_id } => post::view_post(client, &post_id).await,
483 Commands::DeletePost { post_id } => post::delete_post(client, &post_id).await,
484 Commands::Upvote { post_id } => post::upvote_post(client, &post_id).await,
485 Commands::Downvote { post_id } => post::downvote_post(client, &post_id).await,
486 Commands::Search {
487 query,
488 type_filter,
489 limit,
490 } => post::search(client, &query, &type_filter, limit).await,
491 Commands::Comments { post_id, sort } => post::comments(client, &post_id, &sort).await,
492 Commands::Comment {
493 post_id,
494 content,
495 content_flag,
496 } => post::create_comment(client, &post_id, content, content_flag, None).await,
497 Commands::ReplyComment {
498 post_id,
499 parent_id,
500 content,
501 } => post::create_comment(client, &post_id, content, None, Some(parent_id)).await,
502 Commands::UpvoteComment { comment_id } => post::upvote_comment(client, &comment_id).await,
503
504 Commands::Submolts { sort, limit } => submolt::list_submolts(client, &sort, limit).await,
506 Commands::Submolt { name, sort, limit } => {
507 submolt::view_submolt(client, &name, &sort, limit).await
508 }
509 Commands::CreateSubmolt {
510 name,
511 display_name,
512 description,
513 allow_crypto,
514 } => submolt::create_submolt(client, &name, &display_name, description, allow_crypto).await,
515 Commands::Subscribe { name } => submolt::subscribe(client, &name).await,
516 Commands::Unsubscribe { name } => submolt::unsubscribe(client, &name).await,
517 Commands::PinPost { post_id } => submolt::pin_post(client, &post_id).await,
518 Commands::UnpinPost { post_id } => submolt::unpin_post(client, &post_id).await,
519 Commands::SubmoltSettings {
520 name,
521 description,
522 banner_color,
523 theme_color,
524 } => submolt::update_settings(client, &name, description, banner_color, theme_color).await,
525 Commands::SubmoltMods { name } => submolt::list_moderators(client, &name).await,
526 Commands::SubmoltModAdd {
527 name,
528 agent_name,
529 role,
530 } => submolt::add_moderator(client, &name, &agent_name, &role).await,
531 Commands::SubmoltModRemove { name, agent_name } => {
532 submolt::remove_moderator(client, &name, &agent_name).await
533 }
534
535 Commands::DmCheck => dm::check_dms(client).await,
537 Commands::DmRequests => dm::list_dm_requests(client).await,
538 Commands::DmList => dm::list_conversations(client).await,
539 Commands::DmRead { conversation_id } => dm::read_dm(client, &conversation_id).await,
540 Commands::DmSend {
541 conversation_id,
542 message,
543 needs_human,
544 } => dm::send_dm(client, &conversation_id, message, needs_human).await,
545 Commands::DmRequest {
546 to,
547 message,
548 by_owner,
549 } => dm::send_request(client, to, message, by_owner).await,
550 Commands::DmApprove { conversation_id } => {
551 dm::approve_request(client, &conversation_id).await
552 }
553 Commands::DmReject {
554 conversation_id,
555 block,
556 } => dm::reject_request(client, &conversation_id, block).await,
557 }
558}