use anyhow::Result; use clap::{Parser, Subcommand}; use std::path::PathBuf; mod analyzer; mod commands; mod doc_generator; mod generator; mod markdown; mod template; mod oauth; mod translator; mod config; mod ai; mod atproto; mod mcp; #[derive(Parser)] #[command(name = "ailog")] #[command(about = "A static blog generator with AI features")] #[command(disable_version_flag = true)] struct Cli { /// Print version information #[arg(short = 'V', long = "version")] version: bool, #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { /// Initialize a new blog Init { /// Path to create the blog #[arg(default_value = ".")] path: PathBuf, }, /// Build the blog Build { /// Path to the blog directory #[arg(default_value = ".")] path: PathBuf, }, /// Create a new post New { /// Title of the post title: String, /// Slug for the post (optional, derived from title if not provided) #[arg(short, long)] slug: Option, /// Post format #[arg(short, long, default_value = "md")] format: String, /// Path to the blog directory #[arg(default_value = ".")] path: PathBuf, }, /// Serve the blog locally Serve { /// Port to serve on #[arg(short, long, default_value = "8080")] port: u16, /// Path to the blog directory #[arg(default_value = ".")] path: PathBuf, }, /// Clean build artifacts Clean { /// Path to the blog directory #[arg(default_value = ".")] path: PathBuf, }, /// Start MCP server for ai.gpt integration Mcp { /// Port to serve MCP on #[arg(short, long, default_value = "8002")] port: u16, /// Path to the blog directory #[arg(default_value = ".")] path: PathBuf, }, /// Generate documentation from code Doc(commands::doc::DocCommand), /// ATProto authentication Auth { #[command(subcommand)] command: AuthCommands, }, /// ATProto stream monitoring Stream { #[command(subcommand)] command: StreamCommands, }, /// OAuth app management Oauth { #[command(subcommand)] command: OauthCommands, }, } #[derive(Subcommand)] enum AuthCommands { /// Initialize OAuth authentication Init { /// Specify PDS server (e.g., syu.is, bsky.social) #[arg(long)] pds: Option, /// Handle/username for authentication #[arg(long)] handle: Option, /// Use password authentication instead of JWT #[arg(long)] password: bool, /// Access JWT token (alternative to password auth) #[arg(long)] access_jwt: Option, /// Refresh JWT token (required with access-jwt) #[arg(long)] refresh_jwt: Option, }, /// Show current authentication status Status, /// Logout and clear credentials Logout, } #[derive(Subcommand)] enum StreamCommands { /// Start monitoring ATProto streams Start { /// Path to the blog project directory project_dir: Option, /// Run as daemon #[arg(short, long)] daemon: bool, /// Enable AI content generation #[arg(long)] ai_generate: bool, }, /// Initialize user list for admin account Init { /// Path to the blog project directory project_dir: Option, /// Handles to add to initial user list (comma-separated) #[arg(long)] handles: Option, }, /// Stop monitoring Stop, /// Show monitoring status Status, /// Test API access to comments collection Test, /// Test user list update functionality TestUserUpdate, /// Test recent comment detection logic TestRecentDetection, /// Test complete polling cycle logic TestPollingCycle, } #[derive(Subcommand)] enum OauthCommands { /// Build OAuth app Build { /// Path to the blog project directory project_dir: PathBuf, }, } #[tokio::main] async fn main() -> Result<()> { let cli = Cli::parse(); // Handle version flag if cli.version { println!("{}", env!("CARGO_PKG_VERSION")); return Ok(()); } // Require subcommand if no version flag let command = cli.command.ok_or_else(|| { anyhow::anyhow!("No subcommand provided. Use --help for usage information.") })?; match command { Commands::Init { path } => { commands::init::execute(path).await?; } Commands::Build { path } => { commands::build::execute(path).await?; } Commands::New { title, slug, format, path } => { std::env::set_current_dir(path)?; commands::new::execute(title, slug, format).await?; } Commands::Serve { port, path } => { std::env::set_current_dir(path)?; commands::serve::execute(port).await?; } Commands::Clean { path } => { std::env::set_current_dir(path)?; commands::clean::execute().await?; } Commands::Mcp { port, path } => { use crate::mcp::McpServer; let server = McpServer::new(path); server.serve(port).await?; } Commands::Doc(doc_cmd) => { doc_cmd.execute(std::env::current_dir()?).await?; } Commands::Auth { command } => { match command { AuthCommands::Init { pds, handle, password, access_jwt, refresh_jwt } => { commands::auth::init_with_options(pds, handle, password, access_jwt, refresh_jwt).await?; } AuthCommands::Status => { commands::auth::status().await?; } AuthCommands::Logout => { commands::auth::logout().await?; } } } Commands::Stream { command } => { match command { StreamCommands::Start { project_dir, daemon, ai_generate } => { commands::stream::start(project_dir, daemon, ai_generate).await?; } StreamCommands::Init { project_dir, handles } => { commands::stream::init_user_list(project_dir, handles).await?; } StreamCommands::Stop => { commands::stream::stop().await?; } StreamCommands::Status => { commands::stream::status().await?; } StreamCommands::Test => { commands::stream::test_api().await?; } StreamCommands::TestUserUpdate => { commands::stream::test_user_update().await?; } StreamCommands::TestRecentDetection => { commands::stream::test_recent_detection().await?; } StreamCommands::TestPollingCycle => { commands::stream::test_polling_cycle().await?; } } } Commands::Oauth { command } => { match command { OauthCommands::Build { project_dir } => { commands::oauth::build(project_dir).await?; } } } } Ok(()) }