Skip to main content

syncable_cli/
cli.rs

1use clap::{Parser, Subcommand, ValueEnum};
2use std::path::PathBuf;
3
4#[derive(Parser)]
5#[command(name = "sync-ctl")]
6#[command(version = env!("CARGO_PKG_VERSION"))]
7#[command(about = "DevOps CLI toolbox for AI coding agents and developers")]
8#[command(
9    long_about = "Analyze tech stacks, scan for security issues and CVEs, validate IaC files, optimize Kubernetes resources, and deploy to cloud providers. Works standalone or through AI coding agent skills (Claude Code, Codex, Gemini CLI, Cursor, Windsurf)."
10)]
11pub struct Cli {
12    #[command(subcommand)]
13    pub command: Commands,
14
15    /// Path to configuration file
16    #[arg(short, long, global = true, value_name = "FILE")]
17    pub config: Option<PathBuf>,
18
19    /// Enable verbose logging (-v for info, -vv for debug, -vvv for trace)
20    #[arg(short, long, global = true, action = clap::ArgAction::Count)]
21    pub verbose: u8,
22
23    /// Suppress all output except errors
24    #[arg(short, long, global = true)]
25    pub quiet: bool,
26
27    /// Output in JSON format where applicable
28    #[arg(long, global = true)]
29    pub json: bool,
30
31    /// Clear the update check cache and force a new check
32    #[arg(long, global = true)]
33    pub clear_update_cache: bool,
34
35    /// Disable telemetry data collection
36    #[arg(long, global = true)]
37    pub disable_telemetry: bool,
38}
39
40#[derive(Subcommand)]
41pub enum Commands {
42    /// Analyze a project and display detected components
43    Analyze {
44        /// Path to the project directory to analyze
45        #[arg(value_name = "PROJECT_PATH")]
46        path: PathBuf,
47
48        /// Output analysis results in JSON format
49        #[arg(short, long)]
50        json: bool,
51
52        /// Show detailed analysis information (legacy format)
53        #[arg(short, long, conflicts_with = "display")]
54        detailed: bool,
55
56        /// Display format for analysis results
57        #[arg(long, value_enum, default_value = "matrix")]
58        display: Option<DisplayFormat>,
59
60        /// Only analyze specific aspects (languages, frameworks, dependencies)
61        #[arg(long, value_delimiter = ',')]
62        only: Option<Vec<String>>,
63
64        /// Color scheme for terminal output (auto-detect, dark, light)
65        #[arg(long, value_enum, default_value = "auto")]
66        color_scheme: Option<ColorScheme>,
67
68        /// Output compressed JSON for AI agent consumption (implies --json)
69        #[arg(long)]
70        agent: bool,
71    },
72
73    /// Generate IaC files for a project
74    Generate {
75        /// Path to the project directory to analyze
76        #[arg(value_name = "PROJECT_PATH")]
77        path: PathBuf,
78
79        /// Output directory for generated files
80        #[arg(short, long, value_name = "OUTPUT_DIR")]
81        output: Option<PathBuf>,
82
83        /// Generate Dockerfile
84        #[arg(long)]
85        dockerfile: bool,
86
87        /// Generate Docker Compose file
88        #[arg(long)]
89        compose: bool,
90
91        /// Generate Terraform configuration
92        #[arg(long)]
93        terraform: bool,
94
95        /// Generate all supported IaC files
96        #[arg(long, conflicts_with_all = ["dockerfile", "compose", "terraform"])]
97        all: bool,
98
99        /// Perform a dry run without creating files
100        #[arg(long)]
101        dry_run: bool,
102
103        /// Overwrite existing files
104        #[arg(long)]
105        force: bool,
106    },
107
108    /// Validate existing IaC files against best practices
109    Validate {
110        /// Path to the directory containing IaC files
111        #[arg(value_name = "PATH")]
112        path: PathBuf,
113
114        /// Types of files to validate
115        #[arg(long, value_delimiter = ',')]
116        types: Option<Vec<String>>,
117
118        /// Fix issues automatically where possible
119        #[arg(long)]
120        fix: bool,
121
122        /// Output compressed JSON for AI agent consumption (implies --json)
123        #[arg(long)]
124        agent: bool,
125    },
126
127    /// Show supported languages and frameworks
128    Support {
129        /// Show only languages
130        #[arg(long)]
131        languages: bool,
132
133        /// Show only frameworks
134        #[arg(long)]
135        frameworks: bool,
136
137        /// Show detailed information
138        #[arg(short, long)]
139        detailed: bool,
140    },
141
142    /// Analyze project dependencies in detail
143    Dependencies {
144        /// Path to the project directory to analyze
145        #[arg(value_name = "PROJECT_PATH")]
146        path: PathBuf,
147
148        /// Show license information for dependencies
149        #[arg(long)]
150        licenses: bool,
151
152        /// Check for known vulnerabilities
153        #[arg(long)]
154        vulnerabilities: bool,
155
156        /// Show only production dependencies
157        #[arg(long, conflicts_with = "dev_only")]
158        prod_only: bool,
159
160        /// Show only development dependencies
161        #[arg(long, conflicts_with = "prod_only")]
162        dev_only: bool,
163
164        /// Output format
165        #[arg(long, value_enum, default_value = "table")]
166        format: OutputFormat,
167
168        /// Output compressed JSON for AI agent consumption (implies --json)
169        #[arg(long)]
170        agent: bool,
171    },
172
173    /// Check dependencies for known vulnerabilities
174    Vulnerabilities {
175        /// Check vulnerabilities in a specific path
176        #[arg(default_value = ".")]
177        path: PathBuf,
178
179        /// Show only vulnerabilities with severity >= threshold
180        #[arg(long, value_enum)]
181        severity: Option<SeverityThreshold>,
182
183        /// Output format
184        #[arg(long, value_enum, default_value = "table")]
185        format: OutputFormat,
186
187        /// Export report to file
188        #[arg(long)]
189        output: Option<PathBuf>,
190
191        /// Output compressed JSON for AI agent consumption (implies --json)
192        #[arg(long)]
193        agent: bool,
194    },
195
196    /// Perform comprehensive security analysis
197    Security {
198        /// Path to the project directory to analyze
199        #[arg(value_name = "PROJECT_PATH", default_value = ".")]
200        path: PathBuf,
201
202        /// Security scan mode (lightning, fast, balanced, thorough, paranoid)
203        #[arg(long, value_enum, default_value = "thorough")]
204        mode: SecurityScanMode,
205
206        /// Include low severity findings
207        #[arg(long)]
208        include_low: bool,
209
210        /// Skip secrets detection
211        #[arg(long)]
212        no_secrets: bool,
213
214        /// Skip code pattern analysis
215        #[arg(long)]
216        no_code_patterns: bool,
217
218        /// Skip infrastructure analysis (not implemented yet)
219        #[arg(long, hide = true)]
220        no_infrastructure: bool,
221
222        /// Skip compliance checks (not implemented yet)
223        #[arg(long, hide = true)]
224        no_compliance: bool,
225
226        /// Compliance frameworks to check (not implemented yet)
227        #[arg(long, value_delimiter = ',', hide = true)]
228        frameworks: Vec<String>,
229
230        /// Output format
231        #[arg(long, value_enum, default_value = "table")]
232        format: OutputFormat,
233
234        /// Export report to file
235        #[arg(long)]
236        output: Option<PathBuf>,
237
238        /// Exit with error code on security findings
239        #[arg(long)]
240        fail_on_findings: bool,
241
242        /// Output compressed JSON for AI agent consumption (implies --json)
243        #[arg(long)]
244        agent: bool,
245    },
246
247    /// Manage vulnerability scanning tools
248    Tools {
249        #[command(subcommand)]
250        command: ToolsCommand,
251    },
252
253    /// Analyze Kubernetes manifests for resource optimization opportunities
254    Optimize {
255        /// Path to Kubernetes manifests (file or directory)
256        #[arg(value_name = "PATH", default_value = ".")]
257        path: PathBuf,
258
259        /// Connect to a live Kubernetes cluster for metrics-based recommendations
260        /// Uses current kubeconfig context, or specify a context name
261        #[arg(long, short = 'k', value_name = "CONTEXT", default_missing_value = "current", num_args = 0..=1)]
262        cluster: Option<String>,
263
264        /// Prometheus URL for historical metrics (e.g., http://localhost:9090)
265        #[arg(long, value_name = "URL")]
266        prometheus: Option<String>,
267
268        /// Target namespace(s) for cluster analysis (comma-separated, or * for all)
269        #[arg(long, short = 'n', value_name = "NAMESPACE")]
270        namespace: Option<String>,
271
272        /// Analysis period for historical metrics (e.g., 7d, 30d)
273        #[arg(long, short = 'p', default_value = "7d")]
274        period: String,
275
276        /// Minimum severity to report (critical, warning, info)
277        #[arg(long, short = 's')]
278        severity: Option<String>,
279
280        /// Minimum waste percentage threshold (0-100)
281        #[arg(long, short = 't')]
282        threshold: Option<u8>,
283
284        /// Safety margin percentage for recommendations (default: 20)
285        #[arg(long)]
286        safety_margin: Option<u8>,
287
288        /// Include info-level suggestions
289        #[arg(long)]
290        include_info: bool,
291
292        /// Include system namespaces (kube-system, etc.)
293        #[arg(long)]
294        include_system: bool,
295
296        /// Output format (table, json, yaml)
297        #[arg(long, value_enum, default_value = "table")]
298        format: OutputFormat,
299
300        /// Write report to file
301        #[arg(long, short = 'o')]
302        output: Option<PathBuf>,
303
304        /// Generate fix suggestions
305        #[arg(long)]
306        fix: bool,
307
308        /// Apply fixes to manifest files (requires --fix or --full with live cluster)
309        #[arg(long, requires = "fix")]
310        apply: bool,
311
312        /// Preview changes without applying (dry-run mode)
313        #[arg(long)]
314        dry_run: bool,
315
316        /// Backup directory for original files before applying fixes
317        #[arg(long, value_name = "DIR")]
318        backup_dir: Option<PathBuf>,
319
320        /// Minimum confidence threshold for auto-apply (0-100, default: 70)
321        #[arg(long, default_value = "70")]
322        min_confidence: u8,
323
324        /// Cloud provider for cost estimation (aws, gcp, azure, onprem)
325        #[arg(long, value_name = "PROVIDER")]
326        cloud_provider: Option<String>,
327
328        /// Region for cloud pricing (e.g., us-east-1, us-central1)
329        #[arg(long, value_name = "REGION", default_value = "us-east-1")]
330        region: String,
331
332        /// Run comprehensive analysis (includes kubelint security checks and helmlint validation)
333        #[arg(long, short = 'f')]
334        full: bool,
335
336        /// Output compressed JSON for AI agent consumption (implies --json)
337        #[arg(long)]
338        agent: bool,
339    },
340
341    /// Retrieve stored output from a previous --agent command
342    Retrieve {
343        /// Reference ID (e.g., "security_a1b2c3d4") or "latest" for most recent
344        #[arg(value_name = "REF_ID")]
345        ref_id: Option<String>,
346
347        /// Filter query (e.g., "severity:critical", "file:path", "section:frameworks")
348        #[arg(long, short = 'q')]
349        query: Option<String>,
350
351        /// List all stored outputs
352        #[arg(long)]
353        list: bool,
354
355        /// Maximum number of results to return (default: 20)
356        #[arg(long, short = 'l', default_value = "20")]
357        limit: usize,
358
359        /// Number of results to skip (for pagination)
360        #[arg(long, default_value = "0")]
361        offset: usize,
362    },
363
364    /// [DEPRECATED] Start an interactive AI chat session. Use AI coding agent skills instead.
365    #[command(hide = true)]
366    Chat {
367        /// Path to the project directory (default: current directory)
368        #[arg(value_name = "PROJECT_PATH", default_value = ".")]
369        path: PathBuf,
370
371        /// LLM provider to use (uses saved preference by default)
372        #[arg(long, value_enum, default_value = "auto")]
373        provider: ChatProvider,
374
375        /// Model to use (e.g., gpt-4o, claude-3-5-sonnet-latest, llama3.2)
376        #[arg(long)]
377        model: Option<String>,
378
379        /// Run a single query instead of interactive mode
380        #[arg(long)]
381        query: Option<String>,
382
383        /// Resume a previous session (accepts: "latest", session number, or UUID)
384        #[arg(long, short = 'r')]
385        resume: Option<String>,
386
387        /// List available sessions for this project and exit
388        #[arg(long)]
389        list_sessions: bool,
390
391        /// Start AG-UI server for frontend connectivity (SSE/WebSocket)
392        #[arg(long)]
393        ag_ui: bool,
394
395        /// AG-UI server port (default: 9090)
396        #[arg(long, default_value = "9090", requires = "ag_ui")]
397        ag_ui_port: u16,
398    },
399
400    /// Authenticate with the Syncable platform
401    Auth {
402        #[command(subcommand)]
403        command: AuthCommand,
404    },
405
406    /// Manage Syncable projects
407    Project {
408        #[command(subcommand)]
409        command: ProjectCommand,
410    },
411
412    /// Manage Syncable organizations
413    Org {
414        #[command(subcommand)]
415        command: OrgCommand,
416    },
417
418    /// Manage environments within a project
419    Env {
420        #[command(subcommand)]
421        command: EnvCommand,
422    },
423
424    /// Deploy services to the Syncable platform (launches wizard by default)
425    Deploy {
426        /// Path to the project directory (default: current directory)
427        #[arg(value_name = "PROJECT_PATH", default_value = ".")]
428        path: PathBuf,
429
430        #[command(subcommand)]
431        command: Option<DeployCommand>,
432    },
433
434    /// [DEPRECATED] Run as dedicated AG-UI agent server. Use AI coding agent skills instead.
435    #[command(hide = true)]
436    Agent {
437        /// Path to the project directory
438        #[arg(value_name = "PROJECT_PATH", default_value = ".")]
439        path: PathBuf,
440
441        /// Port for AG-UI server
442        #[arg(long, short, default_value = "9090")]
443        port: u16,
444
445        /// Host address to bind to
446        #[arg(long, default_value = "127.0.0.1")]
447        host: String,
448
449        /// LLM provider to use
450        #[arg(long, value_enum, default_value = "auto")]
451        provider: ChatProvider,
452
453        /// Model to use
454        #[arg(long)]
455        model: Option<String>,
456    },
457}
458
459#[derive(Subcommand)]
460pub enum ToolsCommand {
461    /// Check which vulnerability scanning tools are installed
462    Status {
463        /// Output format
464        #[arg(long, value_enum, default_value = "table")]
465        format: OutputFormat,
466
467        /// Check tools for specific languages only
468        #[arg(long, value_delimiter = ',')]
469        languages: Option<Vec<String>>,
470    },
471
472    /// Install missing vulnerability scanning tools
473    Install {
474        /// Install tools for specific languages only
475        #[arg(long, value_delimiter = ',')]
476        languages: Option<Vec<String>>,
477
478        /// Also install OWASP Dependency Check (large download)
479        #[arg(long)]
480        include_owasp: bool,
481
482        /// Perform a dry run to show what would be installed
483        #[arg(long)]
484        dry_run: bool,
485
486        /// Skip confirmation prompts
487        #[arg(short, long)]
488        yes: bool,
489    },
490
491    /// Verify that installed tools are working correctly
492    Verify {
493        /// Test tools for specific languages only
494        #[arg(long, value_delimiter = ',')]
495        languages: Option<Vec<String>>,
496
497        /// Show detailed verification output
498        #[arg(short, long)]
499        detailed: bool,
500    },
501
502    /// Show tool installation guides for manual setup
503    Guide {
504        /// Show guide for specific languages only
505        #[arg(long, value_delimiter = ',')]
506        languages: Option<Vec<String>>,
507
508        /// Show platform-specific instructions
509        #[arg(long)]
510        platform: Option<String>,
511    },
512}
513
514/// Authentication subcommands for the Syncable platform
515#[derive(Subcommand)]
516pub enum AuthCommand {
517    /// Log in to Syncable (opens browser for authentication)
518    Login {
519        /// Don't open browser automatically
520        #[arg(long)]
521        no_browser: bool,
522    },
523
524    /// Log out and clear stored credentials
525    Logout,
526
527    /// Show current authentication status
528    Status,
529
530    /// Print current access token (for scripting)
531    Token {
532        /// Print raw token without formatting
533        #[arg(long)]
534        raw: bool,
535    },
536}
537
538/// Project management subcommands
539#[derive(Subcommand)]
540pub enum ProjectCommand {
541    /// List projects in the current organization
542    List {
543        /// Organization ID to list projects from (uses current org if not specified)
544        #[arg(long)]
545        org_id: Option<String>,
546
547        /// Output format
548        #[arg(long, value_enum, default_value = "table")]
549        format: OutputFormat,
550    },
551
552    /// Select a project to work with
553    Select {
554        /// Project ID to select
555        id: String,
556    },
557
558    /// Show current organization and project context
559    Current,
560
561    /// Show details of a project
562    Info {
563        /// Project ID (uses current project if not specified)
564        id: Option<String>,
565    },
566}
567
568/// Organization management subcommands
569#[derive(Subcommand)]
570pub enum OrgCommand {
571    /// List organizations you belong to
572    List {
573        /// Output format
574        #[arg(long, value_enum, default_value = "table")]
575        format: OutputFormat,
576    },
577
578    /// Select an organization to work with
579    Select {
580        /// Organization ID to select
581        id: String,
582    },
583}
584
585/// Environment management subcommands
586#[derive(Subcommand)]
587pub enum EnvCommand {
588    /// List environments in the current project
589    List {
590        /// Output format
591        #[arg(long, value_enum, default_value = "table")]
592        format: OutputFormat,
593    },
594
595    /// Select an environment to work with
596    Select {
597        /// Environment ID to select
598        id: String,
599    },
600}
601
602/// Deployment subcommands
603#[derive(Subcommand)]
604pub enum DeployCommand {
605    /// Launch interactive deployment wizard
606    Wizard {
607        /// Path to the project directory (default: current directory)
608        #[arg(value_name = "PROJECT_PATH", default_value = ".")]
609        path: PathBuf,
610    },
611
612    /// Create a new environment for the current project
613    NewEnv,
614
615    /// Check deployment status
616    Status {
617        /// The deployment task ID (from deploy command output)
618        task_id: String,
619
620        /// Watch for status updates (poll until complete)
621        #[arg(short, long)]
622        watch: bool,
623    },
624
625    /// Preview deployment recommendation (non-interactive, JSON output for agents)
626    Preview {
627        /// Path to project or service subdirectory
628        #[arg(value_name = "PATH", default_value = ".")]
629        path: PathBuf,
630
631        /// Override service name (default: derived from directory name)
632        #[arg(long)]
633        service_name: Option<String>,
634
635        /// Override cloud provider (gcp, hetzner, azure)
636        #[arg(long)]
637        provider: Option<String>,
638
639        /// Override region
640        #[arg(long)]
641        region: Option<String>,
642
643        /// Override machine type
644        #[arg(long)]
645        machine_type: Option<String>,
646
647        /// Override detected port
648        #[arg(long)]
649        port: Option<u16>,
650
651        /// Make service publicly accessible
652        #[arg(long)]
653        public: bool,
654    },
655
656    /// Deploy a service non-interactively (for agents and CI/CD)
657    Run {
658        /// Path to project or service subdirectory
659        #[arg(value_name = "PATH", default_value = ".")]
660        path: PathBuf,
661
662        /// Override service name (default: derived from directory name)
663        #[arg(long)]
664        service_name: Option<String>,
665
666        /// Cloud provider (gcp, hetzner, azure)
667        #[arg(long)]
668        provider: Option<String>,
669
670        /// Region
671        #[arg(long)]
672        region: Option<String>,
673
674        /// Machine type
675        #[arg(long)]
676        machine_type: Option<String>,
677
678        /// Port to expose
679        #[arg(long)]
680        port: Option<u16>,
681
682        /// Make service publicly accessible
683        #[arg(long)]
684        public: bool,
685
686        /// CPU allocation (for GCP/Azure, e.g. "1000m", "2")
687        #[arg(long)]
688        cpu: Option<String>,
689
690        /// Memory allocation (for GCP/Azure, e.g. "512Mi", "2Gi")
691        #[arg(long)]
692        memory: Option<String>,
693
694        /// Min instances/replicas
695        #[arg(long)]
696        min_instances: Option<i32>,
697
698        /// Max instances/replicas
699        #[arg(long)]
700        max_instances: Option<i32>,
701
702        /// Environment variable as KEY=VALUE (non-secret, repeatable)
703        #[arg(long = "env", value_name = "KEY=VALUE")]
704        env_vars: Vec<String>,
705
706        /// Secret key name (user prompted in terminal for value, repeatable)
707        #[arg(long = "secret")]
708        secrets: Vec<String>,
709
710        /// Load environment variables from a .env file
711        #[arg(long)]
712        env_file: Option<PathBuf>,
713    },
714}
715
716#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
717pub enum OutputFormat {
718    Table,
719    Json,
720}
721
722#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
723pub enum DisplayFormat {
724    /// Compact matrix/dashboard view (modern, easy to scan)
725    Matrix,
726    /// Detailed vertical view (legacy format with all details)
727    Detailed,
728    /// Brief summary only
729    Summary,
730}
731
732#[derive(Clone, Copy, Debug, ValueEnum)]
733pub enum ColorScheme {
734    /// Auto-detect terminal background (default)
735    Auto,
736    /// Dark background terminal colors
737    Dark,
738    /// Light background terminal colors
739    Light,
740}
741
742#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
743pub enum SeverityThreshold {
744    Low,
745    Medium,
746    High,
747    Critical,
748}
749
750#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
751pub enum SecurityScanMode {
752    /// Lightning fast scan - critical files only (.env, configs)
753    Lightning,
754    /// Fast scan - smart sampling with priority patterns
755    Fast,
756    /// Balanced scan - good coverage with performance optimizations (recommended)
757    Balanced,
758    /// Thorough scan - comprehensive analysis of all files
759    Thorough,
760    /// Paranoid scan - most comprehensive including low-severity findings
761    Paranoid,
762}
763
764#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
765pub enum ChatProvider {
766    /// OpenAI (GPT-4o, GPT-4, etc.)
767    Openai,
768    /// Anthropic (Claude 3)
769    Anthropic,
770    /// AWS Bedrock (Claude via AWS)
771    Bedrock,
772    /// Ollama (local LLM, no API key needed)
773    Ollama,
774    /// Use saved default from config file
775    #[default]
776    Auto,
777}
778
779impl Cli {
780    /// Initialize logging based on verbosity level
781    pub fn init_logging(&self) {
782        if self.quiet {
783            return;
784        }
785
786        let level = match self.verbose {
787            0 => log::LevelFilter::Warn,
788            1 => log::LevelFilter::Info,
789            2 => log::LevelFilter::Debug,
790            _ => log::LevelFilter::Trace,
791        };
792
793        env_logger::Builder::from_default_env()
794            .filter_level(level)
795            .init();
796    }
797}