1pub mod agent;
2pub mod analyzer;
3pub mod auth; pub mod bedrock; pub mod cli;
6pub mod common;
7pub mod config;
8pub mod error;
9pub mod generator;
10pub mod handlers;
11pub mod telemetry; pub use analyzer::{ProjectAnalysis, analyze_project};
15use cli::Commands;
16pub use error::{IaCGeneratorError, Result};
17pub use generator::{generate_compose, generate_dockerfile, generate_terraform};
18pub use handlers::*;
19pub use telemetry::{TelemetryClient, TelemetryConfig, UserId}; pub const VERSION: &str = env!("CARGO_PKG_VERSION");
23
24pub async fn run_command(command: Commands) -> Result<()> {
25 match command {
26 Commands::Analyze {
27 path,
28 json,
29 detailed,
30 display,
31 only,
32 color_scheme,
33 } => {
34 match handlers::handle_analyze(path, json, detailed, display, only, color_scheme) {
35 Ok(_output) => Ok(()), Err(e) => Err(e),
37 }
38 }
39 Commands::Generate {
40 path,
41 output,
42 dockerfile,
43 compose,
44 terraform,
45 all,
46 dry_run,
47 force,
48 } => handlers::handle_generate(
49 path, output, dockerfile, compose, terraform, all, dry_run, force,
50 ),
51 Commands::Validate { path, types, fix } => handlers::handle_validate(path, types, fix),
52 Commands::Support {
53 languages,
54 frameworks,
55 detailed,
56 } => handlers::handle_support(languages, frameworks, detailed),
57 Commands::Dependencies {
58 path,
59 licenses,
60 vulnerabilities,
61 prod_only,
62 dev_only,
63 format,
64 } => handlers::handle_dependencies(
65 path,
66 licenses,
67 vulnerabilities,
68 prod_only,
69 dev_only,
70 format,
71 )
72 .await
73 .map(|_| ()),
74 Commands::Vulnerabilities {
75 path,
76 severity,
77 format,
78 output,
79 } => handlers::handle_vulnerabilities(path, severity, format, output).await,
80 Commands::Security {
81 path,
82 mode,
83 include_low,
84 no_secrets,
85 no_code_patterns,
86 no_infrastructure,
87 no_compliance,
88 frameworks,
89 format,
90 output,
91 fail_on_findings,
92 } => {
93 handlers::handle_security(
94 path,
95 mode,
96 include_low,
97 no_secrets,
98 no_code_patterns,
99 no_infrastructure,
100 no_compliance,
101 frameworks,
102 format,
103 output,
104 fail_on_findings,
105 )
106 .map(|_| ()) }
108 Commands::Tools { command } => handlers::handle_tools(command).await,
109 Commands::Optimize {
110 path,
111 cluster,
112 prometheus,
113 namespace,
114 period,
115 severity,
116 threshold,
117 safety_margin,
118 include_info,
119 include_system,
120 format,
121 output,
122 fix,
123 full,
124 apply,
125 dry_run,
126 backup_dir,
127 min_confidence,
128 cloud_provider,
129 region,
130 } => {
131 let format_str = match format {
132 cli::OutputFormat::Table => "table",
133 cli::OutputFormat::Json => "json",
134 };
135
136 let options = handlers::OptimizeOptions {
137 cluster,
138 prometheus,
139 namespace,
140 period,
141 severity,
142 threshold,
143 safety_margin,
144 include_info,
145 include_system,
146 format: format_str.to_string(),
147 output: output.map(|p| p.to_string_lossy().to_string()),
148 fix,
149 full,
150 apply,
151 dry_run,
152 backup_dir: backup_dir.map(|p| p.to_string_lossy().to_string()),
153 min_confidence,
154 cloud_provider,
155 region,
156 };
157
158 handlers::handle_optimize(&path, options).await
159 }
160 Commands::Chat {
161 path,
162 provider,
163 model,
164 query,
165 resume,
166 list_sessions: _, } => {
168 use agent::ProviderType;
169 use cli::ChatProvider;
170 use config::load_agent_config;
171
172 if !auth::credentials::is_authenticated() {
174 println!("\n\x1b[1;33m📢 Sign in to use Syncable Agent\x1b[0m");
175 println!(" It's free and costs you nothing!\n");
176 println!(" Run: \x1b[1;36msync-ctl auth login\x1b[0m\n");
177 return Err(error::IaCGeneratorError::Config(
178 error::ConfigError::MissingConfig(
179 "Syncable authentication required".to_string(),
180 ),
181 ));
182 }
183
184 let project_path = path.canonicalize().unwrap_or(path);
185
186 if let Some(ref resume_arg) = resume {
188 use agent::persistence::{SessionSelector, format_relative_time};
189
190 let selector = SessionSelector::new(&project_path);
191 if let Some(session_info) = selector.resolve_session(resume_arg) {
192 let time = format_relative_time(session_info.last_updated);
193 println!(
194 "\nResuming session: {} ({}, {} messages)",
195 session_info.display_name, time, session_info.message_count
196 );
197 println!("Session ID: {}\n", session_info.id);
198
199 match selector.load_conversation(&session_info) {
201 Ok(record) => {
202 println!("--- Previous conversation ---");
204 for msg in record.messages.iter().take(5) {
205 let role = match msg.role {
206 agent::persistence::MessageRole::User => "You",
207 agent::persistence::MessageRole::Assistant => "AI",
208 agent::persistence::MessageRole::System => "System",
209 };
210 let preview = if msg.content.len() > 100 {
211 format!("{}...", &msg.content[..100])
212 } else {
213 msg.content.clone()
214 };
215 println!(" {}: {}", role, preview);
216 }
217 if record.messages.len() > 5 {
218 println!(" ... and {} more messages", record.messages.len() - 5);
219 }
220 println!("--- End of history ---\n");
221 }
223 Err(e) => {
224 eprintln!("Warning: Failed to load session history: {}", e);
225 }
226 }
227 } else {
228 eprintln!(
229 "Session '{}' not found. Use --list-sessions to see available sessions.",
230 resume_arg
231 );
232 return Ok(());
233 }
234 }
235
236 let agent_config = load_agent_config();
238
239 let (provider_type, effective_model) = match provider {
241 ChatProvider::Openai => (ProviderType::OpenAI, model),
242 ChatProvider::Anthropic => (ProviderType::Anthropic, model),
243 ChatProvider::Bedrock => (ProviderType::Bedrock, model),
244 ChatProvider::Ollama => {
245 eprintln!("Ollama support coming soon. Using OpenAI as fallback.");
246 (ProviderType::OpenAI, model)
247 }
248 ChatProvider::Auto => {
249 let saved_provider = match agent_config.default_provider.as_str() {
251 "openai" => ProviderType::OpenAI,
252 "anthropic" => ProviderType::Anthropic,
253 "bedrock" => ProviderType::Bedrock,
254 _ => ProviderType::OpenAI, };
256 let saved_model = if model.is_some() {
258 model
259 } else {
260 agent_config.default_model.clone()
261 };
262 (saved_provider, saved_model)
263 }
264 };
265
266 agent::session::ChatSession::load_api_key_to_env(provider_type);
269
270 if let Some(q) = query {
271 let response =
272 agent::run_query(&project_path, &q, provider_type, effective_model).await?;
273 println!("{}", response);
274 Ok(())
275 } else {
276 agent::run_interactive(&project_path, provider_type, effective_model).await?;
277 Ok(())
278 }
279 }
280 Commands::Auth { command } => {
281 use auth::credentials;
282 use auth::device_flow;
283 use cli::AuthCommand;
284
285 match command {
286 AuthCommand::Login { no_browser } => {
287 device_flow::login(no_browser).await.map_err(|e| {
288 error::IaCGeneratorError::Config(error::ConfigError::ParsingFailed(
289 e.to_string(),
290 ))
291 })
292 }
293 AuthCommand::Logout => {
294 credentials::clear_credentials().map_err(|e| {
295 error::IaCGeneratorError::Config(error::ConfigError::ParsingFailed(
296 e.to_string(),
297 ))
298 })?;
299 println!("✅ Logged out successfully. Credentials cleared.");
300 Ok(())
301 }
302 AuthCommand::Status => {
303 match credentials::get_auth_status() {
304 credentials::AuthStatus::NotAuthenticated => {
305 println!("❌ Not logged in.");
306 println!(" Run: sync-ctl auth login");
307 }
308 credentials::AuthStatus::Expired => {
309 println!("⚠️ Session expired.");
310 println!(" Run: sync-ctl auth login");
311 }
312 credentials::AuthStatus::Authenticated { email, expires_at } => {
313 println!("✅ Logged in");
314 if let Some(e) = email {
315 println!(" Email: {}", e);
316 }
317 if let Some(exp) = expires_at {
318 let now = std::time::SystemTime::now()
319 .duration_since(std::time::UNIX_EPOCH)
320 .map(|d| d.as_secs())
321 .unwrap_or(0);
322 if exp > now {
323 let remaining = exp - now;
324 let days = remaining / 86400;
325 let hours = (remaining % 86400) / 3600;
326 println!(" Expires in: {}d {}h", days, hours);
327 }
328 }
329 }
330 }
331 Ok(())
332 }
333 AuthCommand::Token { raw } => match credentials::get_access_token() {
334 Some(token) => {
335 if raw {
336 print!("{}", token);
337 } else {
338 println!("Access Token: {}", token);
339 }
340 Ok(())
341 }
342 None => {
343 eprintln!("Not authenticated. Run: sync-ctl auth login");
344 std::process::exit(1);
345 }
346 },
347 }
348 }
349 }
350}