1pub mod account;
7pub mod dm;
8pub mod post;
9pub mod submolt;
10
11use crate::api::client::MoltbookClient;
12use crate::api::error::ApiError;
13use clap::{Parser, Subcommand};
14use colored::Colorize;
15
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
46#[derive(Subcommand, Debug)]
47pub enum Commands {
48 Init {
50 #[arg(short, long)]
52 api_key: Option<String>,
53
54 #[arg(short, long)]
56 name: Option<String>,
57 },
58
59 Register {
61 #[arg(short, long)]
63 name: Option<String>,
64
65 #[arg(short, long)]
67 description: Option<String>,
68 },
69
70 Profile,
72
73 Feed {
75 #[arg(short, long, default_value = "hot")]
77 sort: String,
78
79 #[arg(short, long, default_value = "25")]
80 limit: u64,
81 },
82
83 Global {
85 #[arg(short, long, default_value = "hot")]
87 sort: String,
88
89 #[arg(short, long, default_value = "25")]
90 limit: u64,
91 },
92
93 Post {
95 #[arg(short, long)]
97 title: Option<String>,
98
99 #[arg(short, long)]
101 content: Option<String>,
102
103 #[arg(short, long)]
105 url: Option<String>,
106
107 #[arg(short, long)]
109 submolt: Option<String>,
110
111 #[arg(index = 1)]
113 title_pos: Option<String>,
114
115 #[arg(index = 2)]
117 submolt_pos: Option<String>,
118
119 #[arg(index = 3)]
121 content_pos: Option<String>,
122
123 #[arg(index = 4)]
125 url_pos: Option<String>,
126 },
127
128 Submolt {
130 name: String,
132
133 #[arg(short, long, default_value = "hot")]
135 sort: String,
136
137 #[arg(short, long, default_value = "25")]
138 limit: u64,
139 },
140
141 ViewPost {
143 post_id: String,
145 },
146
147 Comments {
149 post_id: String,
151
152 #[arg(short, long, default_value = "top")]
154 sort: String,
155 },
156
157 Comment {
159 post_id: String,
161
162 content: Option<String>,
164
165 #[arg(short, long = "content")]
167 content_flag: Option<String>,
168
169 #[arg(short, long)]
171 parent: Option<String>,
172 },
173
174 Upvote {
176 post_id: String,
178 },
179
180 Downvote {
182 post_id: String,
184 },
185
186 DeletePost {
188 post_id: String,
190 },
191
192 UpvoteComment {
194 comment_id: String,
196 },
197
198 Verify {
200 #[arg(short, long)]
202 code: String,
203
204 #[arg(short, long)]
206 solution: String,
207 },
208
209 Search {
211 query: String,
213
214 #[arg(short, long, default_value = "all")]
215 type_filter: String,
216
217 #[arg(short, long, default_value = "20")]
218 limit: u64,
219 },
220
221 Submolts {
223 #[arg(short, long, default_value = "hot")]
225 sort: String,
226
227 #[arg(short, long, default_value = "50")]
228 limit: u64,
229 },
230
231 CreateSubmolt {
233 name: String,
235 display_name: String,
237 #[arg(short, long)]
239 description: Option<String>,
240 #[arg(long)]
242 allow_crypto: bool,
243 },
244
245 Subscribe {
247 name: String,
249 },
250
251 Unsubscribe {
253 name: String,
255 },
256
257 Follow {
259 name: String,
261 },
262
263 Unfollow {
265 name: String,
267 },
268
269 ViewProfile {
271 name: String,
273 },
274
275 UpdateProfile {
277 description: String,
279 },
280
281 UploadAvatar {
283 path: std::path::PathBuf,
285 },
286
287 RemoveAvatar,
289
290 SetupOwnerEmail {
292 email: String,
294 },
295
296 Heartbeat,
298
299 Status,
301
302 DmCheck,
305
306 DmRequests,
308
309 DmRequest {
311 #[arg(short, long)]
313 to: Option<String>,
314
315 #[arg(short, long)]
317 message: Option<String>,
318
319 #[arg(long)]
321 by_owner: bool,
322 },
323
324 DmApprove {
326 conversation_id: String,
328 },
329
330 DmReject {
332 conversation_id: String,
334
335 #[arg(long)]
337 block: bool,
338 },
339
340 DmList,
342
343 DmRead {
345 conversation_id: String,
347 },
348
349 DmSend {
351 conversation_id: String,
353
354 #[arg(short, long)]
356 message: Option<String>,
357
358 #[arg(long)]
360 needs_human: bool,
361 },
362
363 PinPost {
365 post_id: String,
367 },
368
369 UnpinPost {
371 post_id: String,
373 },
374
375 SubmoltSettings {
377 name: String,
379 #[arg(short, long)]
381 description: Option<String>,
382 #[arg(long)]
384 banner_color: Option<String>,
385 #[arg(long)]
387 theme_color: Option<String>,
388 },
389
390 SubmoltMods {
392 name: String,
394 },
395
396 SubmoltModAdd {
398 name: String,
400 agent_name: String,
402 #[arg(long, default_value = "moderator")]
404 role: String,
405 },
406
407 SubmoltModRemove {
409 name: String,
411 agent_name: String,
413 },
414}
415
416pub use account::{init, register_command};
418
419pub async fn execute(command: Commands, client: &MoltbookClient) -> Result<(), ApiError> {
423
424 match command {
425 Commands::Init { .. } => {
426 println!("{}", "Configuration already initialized.".yellow());
427 Ok(())
428 }
429 Commands::Register { .. } => {
430 unreachable!("Register command handled in main.rs");
431 }
432 Commands::Profile => account::view_my_profile(client).await,
434 Commands::Status => account::status(client).await,
435 Commands::Heartbeat => account::heartbeat(client).await,
436 Commands::ViewProfile { name } => account::view_agent_profile(client, &name).await,
437 Commands::UpdateProfile { description } => {
438 account::update_profile(client, &description).await
439 }
440 Commands::UploadAvatar { path } => account::upload_avatar(client, &path).await,
441 Commands::RemoveAvatar => account::remove_avatar(client).await,
442 Commands::Follow { name } => account::follow(client, &name).await,
443 Commands::Unfollow { name } => account::unfollow(client, &name).await,
444 Commands::SetupOwnerEmail { email } => account::setup_owner_email(client, &email).await,
445 Commands::Verify { code, solution } => account::verify(client, &code, &solution).await,
446
447 Commands::Feed { sort, limit } => post::feed(client, &sort, limit).await,
449 Commands::Global { sort, limit } => post::global_feed(client, &sort, limit).await,
450 Commands::Post {
451 title,
452 content,
453 url,
454 submolt,
455 title_pos,
456 submolt_pos,
457 content_pos,
458 url_pos,
459 } => {
460 post::create_post(
461 client,
462 post::PostParams {
463 title,
464 content,
465 url,
466 submolt,
467 title_pos,
468 submolt_pos,
469 content_pos,
470 url_pos,
471 },
472 )
473 .await
474 }
475 Commands::ViewPost { post_id } => post::view_post(client, &post_id).await,
476 Commands::DeletePost { post_id } => post::delete_post(client, &post_id).await,
477 Commands::Upvote { post_id } => post::upvote_post(client, &post_id).await,
478 Commands::Downvote { post_id } => post::downvote_post(client, &post_id).await,
479 Commands::Search {
480 query,
481 type_filter,
482 limit,
483 } => post::search(client, &query, &type_filter, limit).await,
484 Commands::Comments { post_id, sort } => post::comments(client, &post_id, &sort).await,
485 Commands::Comment {
486 post_id,
487 content,
488 content_flag,
489 parent,
490 } => post::create_comment(client, &post_id, content, content_flag, parent).await,
491 Commands::UpvoteComment { comment_id } => post::upvote_comment(client, &comment_id).await,
492
493 Commands::Submolts { sort, limit } => submolt::list_submolts(client, &sort, limit).await,
495 Commands::Submolt { name, sort, limit } => {
496 submolt::view_submolt(client, &name, &sort, limit).await
497 }
498 Commands::CreateSubmolt {
499 name,
500 display_name,
501 description,
502 allow_crypto,
503 } => submolt::create_submolt(client, &name, &display_name, description, allow_crypto).await,
504 Commands::Subscribe { name } => submolt::subscribe(client, &name).await,
505 Commands::Unsubscribe { name } => submolt::unsubscribe(client, &name).await,
506 Commands::PinPost { post_id } => submolt::pin_post(client, &post_id).await,
507 Commands::UnpinPost { post_id } => submolt::unpin_post(client, &post_id).await,
508 Commands::SubmoltSettings {
509 name,
510 description,
511 banner_color,
512 theme_color,
513 } => submolt::update_settings(client, &name, description, banner_color, theme_color).await,
514 Commands::SubmoltMods { name } => submolt::list_moderators(client, &name).await,
515 Commands::SubmoltModAdd {
516 name,
517 agent_name,
518 role,
519 } => submolt::add_moderator(client, &name, &agent_name, &role).await,
520 Commands::SubmoltModRemove { name, agent_name } => {
521 submolt::remove_moderator(client, &name, &agent_name).await
522 }
523
524 Commands::DmCheck => dm::check_dms(client).await,
526 Commands::DmRequests => dm::list_dm_requests(client).await,
527 Commands::DmList => dm::list_conversations(client).await,
528 Commands::DmRead { conversation_id } => dm::read_dm(client, &conversation_id).await,
529 Commands::DmSend {
530 conversation_id,
531 message,
532 needs_human,
533 } => dm::send_dm(client, &conversation_id, message, needs_human).await,
534 Commands::DmRequest {
535 to,
536 message,
537 by_owner,
538 } => dm::send_request(client, to, message, by_owner).await,
539 Commands::DmApprove { conversation_id } => {
540 dm::approve_request(client, &conversation_id).await
541 }
542 Commands::DmReject {
543 conversation_id,
544 block,
545 } => dm::reject_request(client, &conversation_id, block).await,
546 }
547}