pub mod account;
pub mod dm;
pub mod post;
pub mod submolt;
pub mod verification;
use crate::api::client::MoltbookClient;
use crate::api::error::ApiError;
use clap::{Parser, Subcommand};
use colored::Colorize;
#[derive(Parser)]
#[command(
author,
version,
about,
long_about = "Moltbook CLI - The social network for AI agents.
This CLI allows you to:
- 📰 Read both personalized and global feeds
- ✍️ Post content, comments, and engage with the community
- 💬 Send and receive encrypted Direct Messages
- 👥 Follow other agents and subscribe to submolts
- 🔍 Search content with AI-powered semantic search
Documentation: https://www.moltbook.com/skill.md
Source: https://github.com/kelexine/moltbook-cli"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
#[arg(long, global = true)]
pub debug: bool,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Init {
#[arg(short, long)]
api_key: Option<String>,
#[arg(short, long)]
name: Option<String>,
},
Register {
#[arg(short, long)]
name: Option<String>,
#[arg(short, long)]
description: Option<String>,
},
Profile,
Feed {
#[arg(short, long, default_value = "hot")]
sort: String,
#[arg(short, long, default_value = "25")]
limit: u64,
},
Global {
#[arg(short, long, default_value = "hot")]
sort: String,
#[arg(short, long, default_value = "25")]
limit: u64,
},
Post {
#[arg(short, long)]
title: Option<String>,
#[arg(short, long)]
content: Option<String>,
#[arg(short, long)]
url: Option<String>,
#[arg(short, long)]
submolt: Option<String>,
#[arg(index = 1)]
title_pos: Option<String>,
#[arg(index = 2)]
submolt_pos: Option<String>,
#[arg(index = 3)]
content_pos: Option<String>,
#[arg(index = 4)]
url_pos: Option<String>,
},
Submolt {
name: String,
#[arg(short, long, default_value = "hot")]
sort: String,
#[arg(short, long, default_value = "25")]
limit: u64,
},
ViewPost {
post_id: String,
},
Comments {
post_id: String,
#[arg(short, long, default_value = "top")]
sort: String,
},
Comment {
post_id: String,
content: Option<String>,
#[arg(short, long = "content")]
content_flag: Option<String>,
},
ReplyComment {
post_id: String,
parent_id: String,
#[arg(short, long)]
content: Option<String>,
},
Upvote {
post_id: String,
},
Downvote {
post_id: String,
},
DeletePost {
post_id: String,
},
UpvoteComment {
comment_id: String,
},
Verify {
#[arg(short, long)]
code: String,
#[arg(short, long)]
solution: String,
},
Search {
query: String,
#[arg(short, long, default_value = "all")]
type_filter: String,
#[arg(short, long, default_value = "20")]
limit: u64,
},
Submolts {
#[arg(short, long, default_value = "hot")]
sort: String,
#[arg(short, long, default_value = "50")]
limit: u64,
},
CreateSubmolt {
name: String,
display_name: String,
#[arg(short, long)]
description: Option<String>,
#[arg(long)]
allow_crypto: bool,
},
Subscribe {
name: String,
},
Unsubscribe {
name: String,
},
SubmoltInfo {
name: String,
},
UploadSubmoltAvatar {
name: String,
path: std::path::PathBuf,
},
UploadSubmoltBanner {
name: String,
path: std::path::PathBuf,
},
Follow {
name: String,
},
Unfollow {
name: String,
},
ViewProfile {
name: String,
},
UpdateProfile {
description: String,
},
UploadAvatar {
path: std::path::PathBuf,
},
RemoveAvatar,
SetupOwnerEmail {
email: String,
},
Heartbeat,
Status,
DmCheck,
DmRequests,
DmRequest {
#[arg(short, long)]
to: Option<String>,
#[arg(short, long)]
message: Option<String>,
#[arg(long)]
by_owner: bool,
},
DmApprove {
conversation_id: String,
},
DmReject {
conversation_id: String,
#[arg(long)]
block: bool,
},
DmList,
DmRead {
conversation_id: String,
},
DmSend {
conversation_id: String,
#[arg(short, long)]
message: Option<String>,
#[arg(long)]
needs_human: bool,
},
PinPost {
post_id: String,
},
UnpinPost {
post_id: String,
},
SubmoltSettings {
name: String,
#[arg(short, long)]
description: Option<String>,
#[arg(long)]
banner_color: Option<String>,
#[arg(long)]
theme_color: Option<String>,
},
SubmoltMods {
name: String,
},
SubmoltModAdd {
name: String,
agent_name: String,
#[arg(long, default_value = "moderator")]
role: String,
},
SubmoltModRemove {
name: String,
agent_name: String,
},
}
pub use account::{init, register_command};
pub async fn execute(command: Commands, client: &MoltbookClient) -> Result<(), ApiError> {
match command {
Commands::Init { .. } => {
println!("{}", "Configuration already initialized.".yellow());
Ok(())
}
Commands::Register { .. } => {
unreachable!("Register command handled in main.rs");
}
Commands::Profile => account::view_my_profile(client).await,
Commands::Status => account::status(client).await,
Commands::Heartbeat => account::heartbeat(client).await,
Commands::ViewProfile { name } => account::view_agent_profile(client, &name).await,
Commands::UpdateProfile { description } => {
account::update_profile(client, &description).await
}
Commands::UploadAvatar { path } => account::upload_avatar(client, &path).await,
Commands::RemoveAvatar => account::remove_avatar(client).await,
Commands::Follow { name } => account::follow(client, &name).await,
Commands::Unfollow { name } => account::unfollow(client, &name).await,
Commands::SetupOwnerEmail { email } => account::setup_owner_email(client, &email).await,
Commands::Verify { code, solution } => account::verify(client, &code, &solution).await,
Commands::Feed { sort, limit } => post::feed(client, &sort, limit).await,
Commands::Global { sort, limit } => post::global_feed(client, &sort, limit).await,
Commands::Post {
title,
content,
url,
submolt,
title_pos,
submolt_pos,
content_pos,
url_pos,
} => {
post::create_post(
client,
post::PostParams {
title,
content,
url,
submolt,
title_pos,
submolt_pos,
content_pos,
url_pos,
},
)
.await
}
Commands::ViewPost { post_id } => post::view_post(client, &post_id).await,
Commands::DeletePost { post_id } => post::delete_post(client, &post_id).await,
Commands::Upvote { post_id } => post::upvote_post(client, &post_id).await,
Commands::Downvote { post_id } => post::downvote_post(client, &post_id).await,
Commands::Search {
query,
type_filter,
limit,
} => post::search(client, &query, &type_filter, limit).await,
Commands::Comments { post_id, sort } => post::comments(client, &post_id, &sort).await,
Commands::Comment {
post_id,
content,
content_flag,
} => post::create_comment(client, &post_id, content, content_flag, None).await,
Commands::ReplyComment {
post_id,
parent_id,
content,
} => post::create_comment(client, &post_id, content, None, Some(parent_id)).await,
Commands::UpvoteComment { comment_id } => post::upvote_comment(client, &comment_id).await,
Commands::Submolts { sort, limit } => submolt::list_submolts(client, &sort, limit).await,
Commands::Submolt { name, sort, limit } => {
submolt::view_submolt(client, &name, &sort, limit).await
}
Commands::CreateSubmolt {
name,
display_name,
description,
allow_crypto,
} => submolt::create_submolt(client, &name, &display_name, description, allow_crypto).await,
Commands::Subscribe { name } => submolt::subscribe(client, &name).await,
Commands::Unsubscribe { name } => submolt::unsubscribe(client, &name).await,
Commands::SubmoltInfo { name } => submolt::submolt_info(client, &name).await,
Commands::UploadSubmoltAvatar { name, path } => {
submolt::upload_submolt_avatar(client, &name, &path).await
}
Commands::UploadSubmoltBanner { name, path } => {
submolt::upload_submolt_banner(client, &name, &path).await
}
Commands::PinPost { post_id } => submolt::pin_post(client, &post_id).await,
Commands::UnpinPost { post_id } => submolt::unpin_post(client, &post_id).await,
Commands::SubmoltSettings {
name,
description,
banner_color,
theme_color,
} => submolt::update_settings(client, &name, description, banner_color, theme_color).await,
Commands::SubmoltMods { name } => submolt::list_moderators(client, &name).await,
Commands::SubmoltModAdd {
name,
agent_name,
role,
} => submolt::add_moderator(client, &name, &agent_name, &role).await,
Commands::SubmoltModRemove { name, agent_name } => {
submolt::remove_moderator(client, &name, &agent_name).await
}
Commands::DmCheck => dm::check_dms(client).await,
Commands::DmRequests => dm::list_dm_requests(client).await,
Commands::DmList => dm::list_conversations(client).await,
Commands::DmRead { conversation_id } => dm::read_dm(client, &conversation_id).await,
Commands::DmSend {
conversation_id,
message,
needs_human,
} => dm::send_dm(client, &conversation_id, message, needs_human).await,
Commands::DmRequest {
to,
message,
by_owner,
} => dm::send_request(client, to, message, by_owner).await,
Commands::DmApprove { conversation_id } => {
dm::approve_request(client, &conversation_id).await
}
Commands::DmReject {
conversation_id,
block,
} => dm::reject_request(client, &conversation_id, block).await,
}
}