use anyhow::Result; use clap::{Parser, Subcommand}; use tools_shared::find_config_path_warn_conflicts; mod api_client; mod handlers; mod remote_ops; mod ui_config; use handlers::{ BatchHandler, ChecklistHandler, ConfigHandler, CreateHandler, ExportFormat, ExportHandler, InitHandler, MigrateDbHandler, MigrationHandler, PhaseHandler, ProjectHandler, SbomHandler, SearchHandler, SecurityHandler, StatusHandler, ToolHandler, }; #[derive(Parser)] #[command(name = "workspace")] #[command(about = "Syntaxis")] #[command(long_about = r#"Syntaxis - Orchestrate software syntaxis CONFIGURATION SEARCH: Looks for config in order (uses first found): 1. .project/lifecycle.toml - If using Tools 2. .vapora/lifecycle.toml - If VAPORA project The tool automatically uses the first existing location. Example: If .project/ doesn't exist but .vapora/ does, it will use .vapora/lifecycle.toml automatically. DIRECTORIES: .project/ - Tools installation (create if using Tools) .vapora/ - VAPORA platform configuration .coder/ - Documentation tracking & agent interaction .claude/ - Claude Code integration (skills, commands) PHASES: creation → devel → update → review ↔ status → publish → archive "#)] struct Cli { #[arg(short, long, global = true)] verbose: bool, #[arg( short = 'p', long, global = true, help = "Project name (auto-detected if omitted)" )] project: Option, #[arg( long, global = true, value_name = "PATH", help = "Configuration file path (overrides auto-discovery)" )] config: Option, #[arg(long, global = true, help = "Use remote API instead of local database")] remote: bool, #[arg( long, global = true, value_name = "URL", help = "API server base URL (default: http://localhost:3000)" )] api_url: Option, #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Initialize a new project Init { /// Project path path: Option, /// Project type #[arg(short, long)] r#type: Option, }, /// Create project in database from TOML configuration Create, /// Migrate existing project to lifecycle management Migrate { /// Project path to migrate path: String, /// Skip backup creation #[arg(long)] no_backup: bool, }, /// Show current phase Phase { #[command(subcommand)] subcommand: Option, }, /// Manage tools Tool { #[command(subcommand)] subcommand: Option, }, /// Manage checklists Checklist { #[command(subcommand)] subcommand: Option, }, /// Security management and assessment Security { #[command(subcommand)] subcommand: Option, }, /// Run compliance audit with vulnerability scanning Audit { /// Scan for vulnerabilities using cargo-audit #[arg(long)] audit_vulns: bool, /// Filter vulnerabilities against SBOM components (only show vulns affecting SBOM) #[arg(long)] sbom_filter: bool, /// Show detailed vulnerability analysis with remediation #[arg(long)] detailed: bool, }, /// Show project status Status, /// Show configuration Config { #[command(subcommand)] subcommand: Option, }, /// Batch operations on multiple projects Batch { /// Batch operation file (TOML/JSON) file: String, }, /// Search projects with advanced filters Search { /// Search query query: String, /// Filter by phase #[arg(long)] phase: Option, /// Filter by type #[arg(long)] r#type: Option, /// Sort by field #[arg(long, default_value = "name")] sort: String, /// Sort order (asc/desc) #[arg(long, default_value = "asc")] order: String, }, /// Export project data Export { /// Export format (json, csv, toml, yaml, markdown) - uses config default if not specified #[arg(long)] format: Option, /// Output file path - uses config default if not specified #[arg(long)] output: Option, /// Project to export (optional, all if not specified) #[arg(long)] project: Option, }, /// Import project data Import { /// Import file path file: String, /// Import format (auto-detected if not specified) #[arg(long)] format: Option, /// Overwrite existing projects #[arg(long)] overwrite: bool, /// Dry run (preview without importing) #[arg(long)] dry_run: bool, }, /// Plugin management Plugin { #[command(subcommand)] subcommand: Option, }, /// Generate Software Bill of Materials (SBOM) and dependency tree Sbom { #[command(subcommand)] subcommand: Option, }, /// Manage projects Project { #[command(subcommand)] subcommand: Option, }, /// Migrate databases from local to global or run SQL migrations #[command(name = "migrate-db")] MigrateDb { /// Local database path to migrate (to global) #[arg(value_name = "PATH")] path: Option, /// Scan for local databases instead of migrating #[arg(long)] scan: bool, /// Show migration status #[arg(long)] status: bool, /// Path to SQL migration file to execute #[arg(long, value_name = "FILE")] sql_file: Option, /// Find and run all migration files in data/migration directory #[arg(long, value_name = "ROOT")] run_all: Option, }, } #[derive(Subcommand)] enum PhaseCommands { /// Show current phase Current, /// Transition to new phase Transition { /// Target phase name phase: String, /// Check checklist before transitioning #[arg(long)] check_checklist: bool, }, } #[derive(Subcommand)] enum ToolCommands { /// List all tools List, /// Enable a tool Enable { tool: String }, /// Disable a tool Disable { tool: String }, /// Show tool status Status, /// List available Rust tool templates #[command(name = "list-rust")] ListRust, /// Add a Rust tool template to the project #[command(name = "add-rust-template")] AddRustTemplate { tool: String }, } #[derive(Subcommand)] enum ChecklistCommands { /// Show checklist Show { phase: Option }, /// Mark item complete Complete { item_id: String }, /// Add item to checklist Add { description: String }, /// Show available task types Types, } #[derive(Subcommand)] enum ConfigCommands { /// Show configuration Show, /// Validate configuration Validate, } #[derive(Subcommand)] enum PluginCommands { /// List installed plugins List, /// Install a plugin Install { path: String }, /// Uninstall a plugin Uninstall { name: String }, /// Enable a plugin Enable { name: String }, /// Disable a plugin Disable { name: String }, /// Show plugin info Info { name: String }, } #[derive(Subcommand)] enum SecurityCommands { /// Set security profile for the project Profile { /// Profile type (webapi, clitool, dataprocessing, iot, ml, opensourcelib, enterprise, desktop, mobile, microservice) profile_type: String, }, /// Assess security posture Assess { /// Show detailed recommendations #[arg(long)] detailed: bool, }, } #[derive(Subcommand)] enum SbomCommands { /// Generate Software Bill of Materials in configured format(s) Generate, /// Generate and display dependency tree (cargo tree) Tree, } #[derive(Subcommand)] enum ProjectCommands { /// List all projects in database List, /// Show details of a project Show { /// Project name name: String, }, /// Detect current project from .project/lifecycle.toml Detect, /// Interactively select a project Select, } #[tokio::main] async fn main() -> Result<()> { let cli = Cli::parse(); // Set config path as environment variable for handlers (before loading config) if let Some(config_path) = &cli.config { std::env::set_var("SYNTAXIS_CONFIG_PATH", config_path); } // Load UI configuration after setting environment variable let _ui_config = if let Ok(Some(config_path)) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { std::env::var("SYNTAXIS_CONFIG_PATH").ok() })) { ui_config::UiConfig::load_from_file(&config_path).ok() } else if let Some(config_path) = find_config_path_warn_conflicts("config.toml", None) { ui_config::UiConfig::load_from_file(config_path).ok() } else { None }; // Setup logging if cli.verbose { tracing_subscriber::fmt::init(); } // Note: Logo display is handled by the NuShell wrapper script based on // show_logo_on_empty config. The Rust binary runs after wrapper has already // determined whether to show the logo. This just loads config for consistency. // Initialize remote API client if --remote flag is set if cli.remote { let client = remote_ops::init_api_client(cli.api_url.clone())?; remote_ops::check_api_health(&client).await?; } match cli.command { Commands::Init { path, r#type } => { InitHandler::init(path.as_deref(), r#type.as_deref()).await?; } Commands::Create => { CreateHandler::create_project().await?; } Commands::Migrate { path, no_backup } => { MigrationHandler::migrate(&path, !no_backup).await?; } Commands::Phase { subcommand } => match subcommand { Some(PhaseCommands::Current) => PhaseHandler::show_current()?, Some(PhaseCommands::Transition { phase, check_checklist, }) => { PhaseHandler::transition_to(&phase, check_checklist)?; } None => { println!("Phase command requires a subcommand: current or transition"); PhaseHandler::show_current()?; } }, Commands::Tool { subcommand } => match subcommand { Some(ToolCommands::List) => ToolHandler::list_tools()?, Some(ToolCommands::Enable { tool }) => { ToolHandler::enable_tool(&tool)?; } Some(ToolCommands::Disable { tool }) => { ToolHandler::disable_tool(&tool)?; } Some(ToolCommands::Status) => ToolHandler::show_status()?, Some(ToolCommands::ListRust) => ToolHandler::list_rust_tools()?, Some(ToolCommands::AddRustTemplate { tool }) => { ToolHandler::add_rust_tool(&tool)?; } None => { println!("Tool command requires a subcommand"); ToolHandler::list_tools()?; } }, Commands::Checklist { subcommand } => match subcommand { Some(ChecklistCommands::Show { phase }) => { ChecklistHandler::show_checklist(phase.as_deref()).await?; } Some(ChecklistCommands::Complete { item_id }) => { ChecklistHandler::mark_complete(&item_id).await?; } Some(ChecklistCommands::Add { description }) => { ChecklistHandler::add_item(&description).await?; } Some(ChecklistCommands::Types) => { ChecklistHandler::show_types_help()?; } None => { ChecklistHandler::show_checklist(None).await?; } }, Commands::Security { subcommand } => match subcommand { Some(SecurityCommands::Profile { profile_type }) => { SecurityHandler::set_security_profile(&profile_type)?; } Some(SecurityCommands::Assess { detailed }) => { SecurityHandler::assess_security(detailed)?; } None => println!("Security command requires a subcommand"), }, Commands::Audit { audit_vulns, sbom_filter, detailed, } => { SecurityHandler::run_audit(audit_vulns, sbom_filter, detailed).await?; } Commands::Status => StatusHandler::show_status()?, Commands::Config { subcommand } => match subcommand { Some(ConfigCommands::Show) => ConfigHandler::show_config()?, Some(ConfigCommands::Validate) => ConfigHandler::validate_config()?, None => { println!("Config command requires a subcommand"); ConfigHandler::show_config()?; } }, Commands::Batch { file } => { let batch = BatchHandler::load_from_file(&file)?; let results = BatchHandler::execute(&batch)?; println!( "Batch operation completed: {} successful, {} failed", results.iter().filter(|r| r.success).count(), results.iter().filter(|r| !r.success).count() ); } Commands::Search { query, phase, r#type, sort, order, } => { let mut filter = SearchHandler::parse_query(&query); if let Some(p) = phase { filter.phase = Some(p); } if let Some(t) = r#type { filter.project_type = Some(t); } filter.sort = sort; filter.order = order; println!( "Search filters: phase={:?}, type={:?}, query={:?}", filter.phase, filter.project_type, filter.query ); } Commands::Export { format, output, project: _project, } => { // Parse format if provided let export_format = format .as_ref() .map(|f| ExportFormat::from_str(f)) .transpose()?; // Find config file let config_path = find_config_path_warn_conflicts( "config.toml", cli.config.as_ref().map(|s| std::path::Path::new(s)), ) .ok_or_else(|| { anyhow::anyhow!( "Configuration not found. Please create:\n \ - .syntaxis/config.toml (local), or\n \ - .vapora/config.toml (local - takes precedence), or\n \ - ~/.syntaxis/config.toml (global), or\n \ - ~/.vapora/config.toml (global - takes precedence)\n\n \ Or provide explicit path with --config PATH\n \ Or run: syntaxis init" ) })?; ExportHandler::execute_export(&config_path, export_format, output.as_deref(), None) .await?; } Commands::Import { file, format, overwrite, dry_run, } => { let import_format = if let Some(fmt) = format { handlers::ExportFormat::from_str(&fmt)? } else if file.ends_with(".json") { handlers::ExportFormat::Json } else if file.ends_with(".csv") { handlers::ExportFormat::Csv } else if file.ends_with(".toml") { handlers::ExportFormat::Toml } else { handlers::ExportFormat::Json }; println!("Importing from {}", file); println!( "Format: {:?}, Overwrite: {}, Dry run: {}", import_format, overwrite, dry_run ); println!( "Import configuration: source_file={}, format={:?}, overwrite={}, dry_run={}", file, import_format, overwrite, dry_run ); } Commands::Plugin { subcommand } => match subcommand { Some(PluginCommands::List) => { println!("Plugin management not yet fully implemented"); } Some(PluginCommands::Install { path }) => { println!("Installing plugin from: {}", path); } Some(PluginCommands::Uninstall { name }) => { println!("Uninstalling plugin: {}", name); } Some(PluginCommands::Enable { name }) => { println!("Enabling plugin: {}", name); } Some(PluginCommands::Disable { name }) => { println!("Disabling plugin: {}", name); } Some(PluginCommands::Info { name }) => { println!("Plugin info: {}", name); } None => { println!("Plugin command requires a subcommand"); } }, Commands::Sbom { subcommand } => match subcommand { Some(SbomCommands::Generate) => { SbomHandler::generate_sbom()?; } Some(SbomCommands::Tree) => { SbomHandler::generate_tree()?; } None => { println!("SBOM command requires a subcommand: generate or tree"); SbomHandler::generate_sbom()?; } }, Commands::Project { subcommand } => match subcommand { Some(ProjectCommands::List) => { ProjectHandler::list_projects().await?; } Some(ProjectCommands::Show { name }) => { ProjectHandler::show_project(&name).await?; } Some(ProjectCommands::Detect) => { ProjectHandler::detect_current()?; } Some(ProjectCommands::Select) => { let selected = ProjectHandler::select_from_list().await?; println!("\n✅ Selected project: {}", selected); } None => { println!("Project command requires a subcommand: list, show, detect, or select"); ProjectHandler::list_projects().await?; } }, Commands::MigrateDb { path, scan, status, sql_file, run_all, } => { if status { MigrateDbHandler::show_status().await?; } else if scan { let root = path.as_deref().unwrap_or("."); MigrateDbHandler::find_local_databases(root).await?; } else if let Some(db_path) = path { MigrateDbHandler::migrate_to_global(db_path.as_str()).await?; } else if let Some(sql_path) = sql_file { MigrateDbHandler::run_migration_file(&sql_path).await?; } else if let Some(root_path) = run_all { MigrateDbHandler::run_all_migrations(&root_path).await?; } else { println!("migrate-db requires one of: , --scan, --status, --sql-file , or --run-all "); MigrateDbHandler::show_status().await?; } } } Ok(()) }