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::Chat {
110 path,
111 provider,
112 model,
113 query,
114 resume,
115 list_sessions: _, } => {
117 use agent::ProviderType;
118 use cli::ChatProvider;
119 use config::load_agent_config;
120
121 if !auth::credentials::is_authenticated() {
123 println!("\n\x1b[1;33m📢 Sign in to use Syncable Agent\x1b[0m");
124 println!(" It's free and costs you nothing!\n");
125 println!(" Run: \x1b[1;36msync-ctl auth login\x1b[0m\n");
126 return Err(error::IaCGeneratorError::Config(
127 error::ConfigError::MissingConfig(
128 "Syncable authentication required".to_string(),
129 ),
130 ));
131 }
132
133 let project_path = path.canonicalize().unwrap_or(path);
134
135 if let Some(ref resume_arg) = resume {
137 use agent::persistence::{SessionSelector, format_relative_time};
138
139 let selector = SessionSelector::new(&project_path);
140 if let Some(session_info) = selector.resolve_session(resume_arg) {
141 let time = format_relative_time(session_info.last_updated);
142 println!(
143 "\nResuming session: {} ({}, {} messages)",
144 session_info.display_name, time, session_info.message_count
145 );
146 println!("Session ID: {}\n", session_info.id);
147
148 match selector.load_conversation(&session_info) {
150 Ok(record) => {
151 println!("--- Previous conversation ---");
153 for msg in record.messages.iter().take(5) {
154 let role = match msg.role {
155 agent::persistence::MessageRole::User => "You",
156 agent::persistence::MessageRole::Assistant => "AI",
157 agent::persistence::MessageRole::System => "System",
158 };
159 let preview = if msg.content.len() > 100 {
160 format!("{}...", &msg.content[..100])
161 } else {
162 msg.content.clone()
163 };
164 println!(" {}: {}", role, preview);
165 }
166 if record.messages.len() > 5 {
167 println!(" ... and {} more messages", record.messages.len() - 5);
168 }
169 println!("--- End of history ---\n");
170 }
172 Err(e) => {
173 eprintln!("Warning: Failed to load session history: {}", e);
174 }
175 }
176 } else {
177 eprintln!(
178 "Session '{}' not found. Use --list-sessions to see available sessions.",
179 resume_arg
180 );
181 return Ok(());
182 }
183 }
184
185 let agent_config = load_agent_config();
187
188 let (provider_type, effective_model) = match provider {
190 ChatProvider::Openai => (ProviderType::OpenAI, model),
191 ChatProvider::Anthropic => (ProviderType::Anthropic, model),
192 ChatProvider::Bedrock => (ProviderType::Bedrock, model),
193 ChatProvider::Ollama => {
194 eprintln!("Ollama support coming soon. Using OpenAI as fallback.");
195 (ProviderType::OpenAI, model)
196 }
197 ChatProvider::Auto => {
198 let saved_provider = match agent_config.default_provider.as_str() {
200 "openai" => ProviderType::OpenAI,
201 "anthropic" => ProviderType::Anthropic,
202 "bedrock" => ProviderType::Bedrock,
203 _ => ProviderType::OpenAI, };
205 let saved_model = if model.is_some() {
207 model
208 } else {
209 agent_config.default_model.clone()
210 };
211 (saved_provider, saved_model)
212 }
213 };
214
215 agent::session::ChatSession::load_api_key_to_env(provider_type);
218
219 if let Some(q) = query {
220 let response =
221 agent::run_query(&project_path, &q, provider_type, effective_model).await?;
222 println!("{}", response);
223 Ok(())
224 } else {
225 agent::run_interactive(&project_path, provider_type, effective_model).await?;
226 Ok(())
227 }
228 }
229 Commands::Auth { command } => {
230 use auth::credentials;
231 use auth::device_flow;
232 use cli::AuthCommand;
233
234 match command {
235 AuthCommand::Login { no_browser } => {
236 device_flow::login(no_browser).await.map_err(|e| {
237 error::IaCGeneratorError::Config(error::ConfigError::ParsingFailed(
238 e.to_string(),
239 ))
240 })
241 }
242 AuthCommand::Logout => {
243 credentials::clear_credentials().map_err(|e| {
244 error::IaCGeneratorError::Config(error::ConfigError::ParsingFailed(
245 e.to_string(),
246 ))
247 })?;
248 println!("✅ Logged out successfully. Credentials cleared.");
249 Ok(())
250 }
251 AuthCommand::Status => {
252 match credentials::get_auth_status() {
253 credentials::AuthStatus::NotAuthenticated => {
254 println!("❌ Not logged in.");
255 println!(" Run: sync-ctl auth login");
256 }
257 credentials::AuthStatus::Expired => {
258 println!("⚠️ Session expired.");
259 println!(" Run: sync-ctl auth login");
260 }
261 credentials::AuthStatus::Authenticated { email, expires_at } => {
262 println!("✅ Logged in");
263 if let Some(e) = email {
264 println!(" Email: {}", e);
265 }
266 if let Some(exp) = expires_at {
267 let now = std::time::SystemTime::now()
268 .duration_since(std::time::UNIX_EPOCH)
269 .map(|d| d.as_secs())
270 .unwrap_or(0);
271 if exp > now {
272 let remaining = exp - now;
273 let days = remaining / 86400;
274 let hours = (remaining % 86400) / 3600;
275 println!(" Expires in: {}d {}h", days, hours);
276 }
277 }
278 }
279 }
280 Ok(())
281 }
282 AuthCommand::Token { raw } => match credentials::get_access_token() {
283 Some(token) => {
284 if raw {
285 print!("{}", token);
286 } else {
287 println!("Access Token: {}", token);
288 }
289 Ok(())
290 }
291 None => {
292 eprintln!("Not authenticated. Run: sync-ctl auth login");
293 std::process::exit(1);
294 }
295 },
296 }
297 }
298 }
299}