feat: add workshop-llm-detector
Signed-off-by: Catty Steve <4795515+Catty2014@user.noreply.gitee.com>
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
target
|
target
|
||||||
|
playground
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "rust-tongsuo"]
|
||||||
|
path = rust-tongsuo
|
||||||
|
url = https://github.com/Tongsuo-Project/rust-tongsuo.git
|
||||||
Submodule
+1
Submodule rust-tongsuo added at 6248690a45
@@ -1 +0,0 @@
|
|||||||
/target
|
|
||||||
Generated
-7
@@ -1,7 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "workshop-config-manager"
|
|
||||||
version = "0.1.0"
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "workshop-config-manager"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
println!("Hello, world!");
|
|
||||||
}
|
|
||||||
Generated
+1
-1
@@ -574,7 +574,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "workshop-token"
|
name = "workshop-deviceid"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
extern crate workshop_token;
|
extern crate workshop_deviceid;
|
||||||
use workshop_token::DeviceIdGenerator;
|
use workshop_deviceid::DeviceIdGenerator;
|
||||||
|
|
||||||
// server/src/auth/device_id_example.rs
|
// server/src/auth/device_id_example.rs
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// server/src/auth/device_id_generator.rs
|
// device_id_generator.rs
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
mod device_info;
|
mod device_info;
|
||||||
use crate::device_info::{DeviceInfo, DeviceInfoCollector};
|
use crate::device_info::{DeviceInfo, DeviceInfoCollector};
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
target
|
||||||
Generated
+2045
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "workshop-llm-detector"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "workshop-llm-detector"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "workshop_llm_detector"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
async-trait = "0.1.89"
|
||||||
|
clap = { version = "4.5.51", features = ["derive"] }
|
||||||
|
log = "0.4.28"
|
||||||
|
ollama-rs = "0.3.2"
|
||||||
|
openai_api_rust = "0.1.9"
|
||||||
|
reqwest = { version = "0.12.24", features = ["json"] }
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9.34"
|
||||||
|
thiserror = "2.0.17"
|
||||||
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: "Rust"
|
||||||
|
project_types:
|
||||||
|
- "rust"
|
||||||
|
- "cargo"
|
||||||
|
install_path: "/install"
|
||||||
|
artifacts:
|
||||||
|
- "/install/bin/*"
|
||||||
|
- "/install/lib/*.so"
|
||||||
|
- "/install/lib/*.a"
|
||||||
|
- "/target/release/*"
|
||||||
|
dependencies:
|
||||||
|
deb:
|
||||||
|
- "build-essential"
|
||||||
|
- "curl"
|
||||||
|
- "pkg-config"
|
||||||
|
- "libssl-dev"
|
||||||
|
rpm:
|
||||||
|
- "gcc"
|
||||||
|
- "gcc-c++"
|
||||||
|
- "curl"
|
||||||
|
- "openssl-devel"
|
||||||
|
pacman:
|
||||||
|
- "base-devel"
|
||||||
|
- "curl"
|
||||||
|
- "openssl"
|
||||||
|
environment:
|
||||||
|
RUSTUP_DIST_SERVER: "https://static.rust-lang.org"
|
||||||
|
CARGO_HOME: "/usr/local/cargo"
|
||||||
|
RUSTUP_HOME: "/usr/local/rustup"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: "make"
|
||||||
|
project_types:
|
||||||
|
- "make"
|
||||||
|
- "makefile"
|
||||||
|
install_path: "/install"
|
||||||
|
artifacts:
|
||||||
|
- "/install/*"
|
||||||
|
dependencies:
|
||||||
|
deb:
|
||||||
|
- "build-essential"
|
||||||
|
- "curl"
|
||||||
|
- "pkg-config"
|
||||||
|
- "libssl-dev"
|
||||||
|
rpm:
|
||||||
|
- "gcc"
|
||||||
|
- "gcc-c++"
|
||||||
|
- "curl"
|
||||||
|
- "openssl-devel"
|
||||||
|
pacman:
|
||||||
|
- "base-devel"
|
||||||
|
- "curl"
|
||||||
|
- "openssl"
|
||||||
|
environment:
|
||||||
|
NONE: "none"
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Cookbook {
|
||||||
|
pub name: String,
|
||||||
|
pub project_types: Vec<String>,
|
||||||
|
pub install_path: String,
|
||||||
|
pub artifacts: Vec<String>,
|
||||||
|
pub dependencies: HashMap<String, Vec<String>>, // key: package_type (deb, rpm, etc.)
|
||||||
|
pub environment: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum CookbookError {
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("YAML error: {0}")]
|
||||||
|
Yaml(#[from] serde_yaml::Error),
|
||||||
|
#[error("Cookbook not found for project type: {0}")]
|
||||||
|
NotFound(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CookbookManager {
|
||||||
|
cookbooks: HashMap<String, Cookbook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CookbookManager {
|
||||||
|
pub fn load_from_dir<P: AsRef<Path>>(dir: P) -> Result<Self, CookbookError> {
|
||||||
|
let mut cookbooks = HashMap::new();
|
||||||
|
|
||||||
|
let entries = fs::read_dir(dir)?;
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.extension().map_or(false, |ext| ext == "yaml" || ext == "yml") {
|
||||||
|
if let Some(file_name) = path.file_stem().and_then(|s| s.to_str()) {
|
||||||
|
if file_name.ends_with(".cookbook") {
|
||||||
|
let content = fs::read_to_string(&path)?;
|
||||||
|
let cookbook: Cookbook = serde_yaml::from_str(&content)?;
|
||||||
|
|
||||||
|
// Register all project types for this cookbook
|
||||||
|
for project_type in &cookbook.project_types {
|
||||||
|
cookbooks.insert(project_type.clone(), cookbook.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CookbookManager { cookbooks })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cookbook(&self, project_type: &str) -> Result<&Cookbook, CookbookError> {
|
||||||
|
self.cookbooks
|
||||||
|
.get(project_type)
|
||||||
|
.ok_or_else(|| CookbookError::NotFound(project_type.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
mod cookbook;
|
||||||
|
mod llm;
|
||||||
|
mod llm_output;
|
||||||
|
|
||||||
|
pub use cookbook::{Cookbook, CookbookManager, CookbookError};
|
||||||
|
pub use llm::{LLMClient, ollama::OllamaClient, openai::OpenAIClient};
|
||||||
|
pub use llm_output::LLMOutput;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use anyhow::Result;
|
||||||
|
use serde_yaml;
|
||||||
|
|
||||||
|
pub struct CICDLLMHelper {
|
||||||
|
cookbook_manager: CookbookManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CICDLLMHelper {
|
||||||
|
pub fn new(cookbook_dir: &str) -> Result<Self, CookbookError> {
|
||||||
|
let cookbook_manager = CookbookManager::load_from_dir(cookbook_dir)?;
|
||||||
|
Ok(Self { cookbook_manager })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn analyze_repository(
|
||||||
|
&self,
|
||||||
|
repository_path: &str,
|
||||||
|
additional_files: &[String],
|
||||||
|
llm_client: &dyn LLMClient,
|
||||||
|
package_type: &str,
|
||||||
|
) -> Result<AnalysisResult> {
|
||||||
|
// Read README.md
|
||||||
|
let readme_path = Path::new(repository_path).join("README.md");
|
||||||
|
let readme_content = fs::read_to_string(&readme_path)
|
||||||
|
.unwrap_or_else(|_| "No README.md found".to_string());
|
||||||
|
|
||||||
|
// Read additional files
|
||||||
|
let mut file_contents = Vec::new();
|
||||||
|
for file in additional_files {
|
||||||
|
let file_path = Path::new(repository_path).join(file);
|
||||||
|
if let Ok(content) = fs::read_to_string(&file_path) {
|
||||||
|
file_contents.push(format!("File: {}\n{}\n", file, content));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare prompt for LLM
|
||||||
|
let prompt = self.build_prompt(&readme_content, &file_contents, package_type);
|
||||||
|
|
||||||
|
// Get LLM response
|
||||||
|
let llm_response = llm_client.generate(&prompt).await?;
|
||||||
|
|
||||||
|
println!("LLM Response: {}", llm_response);
|
||||||
|
|
||||||
|
// Parse LLM output
|
||||||
|
let llm_output: LLMOutput = serde_yaml::from_str(&llm_response)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to parse LLM output: {}", e))?;
|
||||||
|
|
||||||
|
// Get cookbook for project type
|
||||||
|
let cookbook = self.cookbook_manager.get_cookbook(&llm_output.project_type)?;
|
||||||
|
|
||||||
|
// Merge cookbook and LLM output
|
||||||
|
let result = self.merge_outputs(&llm_output, cookbook, package_type);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_prompt(&self, readme_content: &str, file_contents: &[String], package_type: &str) -> String {
|
||||||
|
format!(
|
||||||
|
r#"Analyze the following repository files and determine the build configuration.
|
||||||
|
|
||||||
|
README.md:
|
||||||
|
{}
|
||||||
|
|
||||||
|
Additional files:
|
||||||
|
{}
|
||||||
|
|
||||||
|
Please provide the analysis in YAML format with the following structure:
|
||||||
|
project_type: <string>
|
||||||
|
dependencies: <list of package names for {package_type} packages>
|
||||||
|
build_steps: <list of shell commands>
|
||||||
|
artifacts: <list of artifact paths, use /install as base directory>
|
||||||
|
environment: <key-value pairs for environment variables>
|
||||||
|
other: <any other relevant information>
|
||||||
|
|
||||||
|
Focus on identifying:
|
||||||
|
1. The project type (e.g., rust, python, nodejs, cmake, make, etc.)
|
||||||
|
2. Dependencies needed for building (as {package_type} packages)
|
||||||
|
3. Build steps as shell commands
|
||||||
|
4. Artifact paths (output files, typically in /install)
|
||||||
|
5. Environment variables needed for building
|
||||||
|
|
||||||
|
Output only valid YAML, no additional text and remove '```' in output(output as plain text)."#,
|
||||||
|
readme_content,
|
||||||
|
file_contents.join("\n"),
|
||||||
|
package_type = package_type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_outputs(&self, llm_output: &LLMOutput, cookbook: &Cookbook, package_type: &str) -> AnalysisResult {
|
||||||
|
// Merge dependencies
|
||||||
|
let cookbook_deps = cookbook.dependencies.get(package_type)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut all_dependencies = cookbook_deps;
|
||||||
|
all_dependencies.extend_from_slice(&llm_output.dependencies);
|
||||||
|
all_dependencies.sort();
|
||||||
|
all_dependencies.dedup();
|
||||||
|
|
||||||
|
// Merge environment variables
|
||||||
|
let mut all_environment = cookbook.environment.clone();
|
||||||
|
for (key, value) in &llm_output.environment {
|
||||||
|
all_environment.insert(key.clone(), value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge artifacts
|
||||||
|
let mut all_artifacts = cookbook.artifacts.clone();
|
||||||
|
all_artifacts.extend_from_slice(&llm_output.artifacts);
|
||||||
|
all_artifacts.sort();
|
||||||
|
all_artifacts.dedup();
|
||||||
|
|
||||||
|
AnalysisResult {
|
||||||
|
dependencies: all_dependencies,
|
||||||
|
build_steps: llm_output.build_steps.clone(),
|
||||||
|
artifacts: all_artifacts,
|
||||||
|
environment: all_environment,
|
||||||
|
project_type: llm_output.project_type.clone(),
|
||||||
|
other: llm_output.other.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_output_files(&self, result: &AnalysisResult, output_dir: &str) -> Result<()> {
|
||||||
|
let output_path = Path::new(output_dir);
|
||||||
|
|
||||||
|
// Write prebake.tmpl.yaml
|
||||||
|
let prebake_content = serde_yaml::to_string(&PrebakeTemplate {
|
||||||
|
dependencies: &result.dependencies,
|
||||||
|
environment: &result.environment,
|
||||||
|
})?;
|
||||||
|
fs::write(output_path.join("prebake.tmpl.yaml"), prebake_content)?;
|
||||||
|
|
||||||
|
// Write finalize.tmpl.yaml
|
||||||
|
let finalize_content = serde_yaml::to_string(&FinalizeTemplate {
|
||||||
|
artifacts: &result.artifacts,
|
||||||
|
})?;
|
||||||
|
fs::write(output_path.join("finalize.tmpl.yaml"), finalize_content)?;
|
||||||
|
|
||||||
|
// Write build.sh
|
||||||
|
let build_script = result.build_steps.join("\n");
|
||||||
|
fs::write(output_path.join("build.sh"), build_script)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AnalysisResult {
|
||||||
|
pub dependencies: Vec<String>,
|
||||||
|
pub build_steps: Vec<String>,
|
||||||
|
pub artifacts: Vec<String>,
|
||||||
|
pub environment: HashMap<String, String>,
|
||||||
|
pub project_type: String,
|
||||||
|
pub other: HashMap<String, serde_yaml::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct PrebakeTemplate<'a> {
|
||||||
|
dependencies: &'a Vec<String>,
|
||||||
|
environment: &'a HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct FinalizeTemplate<'a> {
|
||||||
|
artifacts: &'a Vec<String>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait LLMClient {
|
||||||
|
async fn generate(&self, prompt: &str) -> Result<String>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mod client;
|
||||||
|
|
||||||
|
pub use client::LLMClient;
|
||||||
|
|
||||||
|
pub mod ollama;
|
||||||
|
pub mod openai;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use anyhow::Result;
|
||||||
|
use ollama_rs::generation::completion::request::GenerationRequest;
|
||||||
|
use ollama_rs::Ollama;
|
||||||
|
|
||||||
|
use super::LLMClient;
|
||||||
|
|
||||||
|
pub struct OllamaClient {
|
||||||
|
client: Ollama,
|
||||||
|
model: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OllamaClient {
|
||||||
|
pub fn new(url: String, model: String) -> Self {
|
||||||
|
let client = Ollama::new(url, 11434);
|
||||||
|
Self { client, model }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LLMClient for OllamaClient {
|
||||||
|
async fn generate(&self, prompt: &str) -> Result<String> {
|
||||||
|
let request = GenerationRequest::new(self.model.clone(), prompt.to_string());
|
||||||
|
let response = self.client.generate(request).await?;
|
||||||
|
Ok(response.response)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use anyhow::Result;
|
||||||
|
use openai_api_rust::*;
|
||||||
|
use openai_api_rust::chat::*;
|
||||||
|
|
||||||
|
use super::LLMClient;
|
||||||
|
|
||||||
|
pub struct OpenAIClient {
|
||||||
|
client: OpenAI,
|
||||||
|
model: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenAIClient {
|
||||||
|
pub fn new(api_key: String, base_url: Option<String>, model: String) -> Self {
|
||||||
|
let auth = Auth::new(api_key.as_str());
|
||||||
|
let client = if let Some(url) = base_url {
|
||||||
|
OpenAI::new(auth, url.as_str())
|
||||||
|
} else {
|
||||||
|
OpenAI::new(auth, "https://api.openai.com/v1/")
|
||||||
|
};
|
||||||
|
Self { client, model }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LLMClient for OpenAIClient {
|
||||||
|
async fn generate(&self, prompt: &str) -> Result<String> {
|
||||||
|
let message = Message {
|
||||||
|
role: Role::User,
|
||||||
|
content: prompt.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = ChatBody {
|
||||||
|
model: self.model.clone(),
|
||||||
|
messages: vec![message],
|
||||||
|
max_tokens: Some(2048),
|
||||||
|
temperature: Some(0.1),
|
||||||
|
top_p: Some(0.1),
|
||||||
|
n: Some(1),
|
||||||
|
stream: Some(false),
|
||||||
|
stop: None,
|
||||||
|
presence_penalty: None,
|
||||||
|
frequency_penalty: None,
|
||||||
|
logit_bias: None,
|
||||||
|
user: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = self.client.chat_completion_create(&body).unwrap();
|
||||||
|
|
||||||
|
if let Some(choice) = response.choices.first() {
|
||||||
|
Ok(choice.message.as_ref().unwrap().content.clone())
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("No response from OpenAI")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct LLMOutput {
|
||||||
|
pub project_type: String,
|
||||||
|
pub dependencies: Vec<String>,
|
||||||
|
pub build_steps: Vec<String>,
|
||||||
|
pub artifacts: Vec<String>,
|
||||||
|
pub environment: HashMap<String, String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub other: HashMap<String, serde_yaml::Value>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use log::error;
|
||||||
|
use workshop_llm_detector::{CICDLLMHelper, LLMClient, OllamaClient, OpenAIClient};
|
||||||
|
// use std::path::Path;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "workshop-llm-detector")]
|
||||||
|
#[command(about = "LLM helper for analyzing repositories")]
|
||||||
|
struct Cli {
|
||||||
|
/// Repository path to analyze
|
||||||
|
repository_path: String,
|
||||||
|
|
||||||
|
/// Additional files to read (comma separated)
|
||||||
|
#[arg(short, long, value_delimiter = ',')]
|
||||||
|
files: Vec<String>,
|
||||||
|
|
||||||
|
/// LLM Provider
|
||||||
|
#[arg(long, default_value = "openai")]
|
||||||
|
provider: String,
|
||||||
|
|
||||||
|
/// Model to use
|
||||||
|
#[arg(long)]
|
||||||
|
model: String,
|
||||||
|
|
||||||
|
/// Model API URL
|
||||||
|
#[arg(long)]
|
||||||
|
model_url: Option<String>,
|
||||||
|
|
||||||
|
/// Package type for dependencies
|
||||||
|
#[arg(long, default_value = "deb")]
|
||||||
|
package_type: String,
|
||||||
|
|
||||||
|
/// Cookbook directory
|
||||||
|
#[arg(long, default_value = "cookbook")]
|
||||||
|
cookbook_dir: String,
|
||||||
|
|
||||||
|
/// Output directory
|
||||||
|
#[arg(short, long, default_value = ".")]
|
||||||
|
output_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let args = Cli::parse();
|
||||||
|
|
||||||
|
// Initialize cookbook manager
|
||||||
|
let helper = CICDLLMHelper::new(&args.cookbook_dir)?;
|
||||||
|
|
||||||
|
// Initialize LLM client
|
||||||
|
let llm_client: Box<dyn LLMClient> = match args.provider.as_str() {
|
||||||
|
"openai" => {
|
||||||
|
let api_key = std::env::var("OPENAI_API_KEY")
|
||||||
|
.expect("OPENAI_API_KEY environment variable must be set");
|
||||||
|
let base_url = args.model_url.clone();
|
||||||
|
Box::new(OpenAIClient::new(api_key, base_url, args.model))
|
||||||
|
}
|
||||||
|
"ollama" => {
|
||||||
|
let url = args.model_url.unwrap_or_else(|| "http://localhost:11434".to_string());
|
||||||
|
Box::new(OllamaClient::new(url, args.model))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Unsupported provider: {}", args.provider);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Analyze repository
|
||||||
|
let result = helper.analyze_repository(
|
||||||
|
&args.repository_path,
|
||||||
|
&args.files,
|
||||||
|
&*llm_client,
|
||||||
|
&args.package_type,
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
println!("Analysis completed for project type: {}", result.project_type);
|
||||||
|
println!("Found {} dependencies", result.dependencies.len());
|
||||||
|
println!("Generated {} build steps", result.build_steps.len());
|
||||||
|
println!("Identified {} artifacts", result.artifacts.len());
|
||||||
|
|
||||||
|
// Write output files
|
||||||
|
helper.write_output_files(&result, &args.output_dir)?;
|
||||||
|
|
||||||
|
println!("Output files written to {}", args.output_dir);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user