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 post::PostParams {
450 title,
451 content,
452 url,
453 submolt,
454 title_pos,
455 submolt_pos,
456 content_pos,
457 url_pos,
458 },
459 )
460 .await
461 }
462 Commands::ViewPost { post_id } => post::view_post(client, &post_id).await,
463 Commands::DeletePost { post_id } => post::delete_post(client, &post_id).await,
464 Commands::Upvote { post_id } => post::upvote_post(client, &post_id).await,
465 Commands::Downvote { post_id } => post::downvote_post(client, &post_id).await,
466 Commands::Search {
467 query,
468 type_filter,
469 limit,
470 } => post::search(client, &query, &type_filter, limit).await,
471 Commands::Comments { post_id, sort } => post::comments(client, &post_id, &sort).await,
472 Commands::Comment {
473 post_id,
474 content,
475 content_flag,
476 parent,
477 } => post::create_comment(client, &post_id, content, content_flag, parent).await,
478 Commands::UpvoteComment { comment_id } => post::upvote_comment(client, &comment_id).await,
479
480 Commands::Submolts { sort, limit } => submolt::list_submolts(client, &sort, limit).await,
482 Commands::Submolt { name, sort, limit } => {
483 submolt::view_submolt(client, &name, &sort, limit).await
484 }
485 Commands::CreateSubmolt {
486 name,
487 display_name,
488 description,
489 allow_crypto,
490 } => submolt::create_submolt(client, &name, &display_name, description, allow_crypto).await,
491 Commands::Subscribe { name } => submolt::subscribe(client, &name).await,
492 Commands::Unsubscribe { name } => submolt::unsubscribe(client, &name).await,
493 Commands::PinPost { post_id } => submolt::pin_post(client, &post_id).await,
494 Commands::UnpinPost { post_id } => submolt::unpin_post(client, &post_id).await,
495 Commands::SubmoltSettings {
496 name,
497 description,
498 banner_color,
499 theme_color,
500 } => submolt::update_settings(client, &name, description, banner_color, theme_color).await,
501 Commands::SubmoltMods { name } => submolt::list_moderators(client, &name).await,
502 Commands::SubmoltModAdd {
503 name,
504 agent_name,
505 role,
506 } => submolt::add_moderator(client, &name, &agent_name, &role).await,
507 Commands::SubmoltModRemove { name, agent_name } => {
508 submolt::remove_moderator(client, &name, &agent_name).await
509 }
510
511 Commands::DmCheck => dm::check_dms(client).await,
513 Commands::DmRequests => dm::list_dm_requests(client).await,
514 Commands::DmList => dm::list_conversations(client).await,
515 Commands::DmRead { conversation_id } => dm::read_dm(client, &conversation_id).await,
516 Commands::DmSend {
517 conversation_id,
518 message,
519 needs_human,
520 } => dm::send_dm(client, &conversation_id, message, needs_human).await,
521 Commands::DmRequest {
522 to,
523 message,
524 by_owner,
525 } => dm::send_request(client, to, message, by_owner).await,
526 Commands::DmApprove { conversation_id } => {
527 dm::approve_request(client, &conversation_id).await
528 }
529 Commands::DmReject {
530 conversation_id,
531 block,
532 } => dm::reject_request(client, &conversation_id, block).await,
533 }
534}