mod json_output;
mod parse;
mod text_output;
use anyhow::Result;
use serde_json::Value;
use std::path::Path;
use rigg_core::resources::ResourceKind;
use crate::cli::OutputFormat;
use crate::commands::load_config_and_env;
use parse::{
add_indexer_dependencies, add_knowledge_source_dependencies, parse_agent_yaml, parse_alias,
parse_data_source, parse_index, parse_indexer, parse_knowledge_base, parse_knowledge_source,
parse_skillset, parse_synonym_map,
};
#[derive(Debug, Clone)]
struct FieldSummary {
name: String,
field_type: String,
is_key: bool,
analyzer: Option<String>,
}
#[derive(Debug, Clone)]
struct IndexSummary {
name: String,
file_path: String,
fields: Vec<FieldSummary>,
vector_profile_count: usize,
has_semantic_config: bool,
}
#[derive(Debug, Clone)]
struct DataSourceSummary {
name: String,
file_path: String,
source_type: String,
container: String,
}
#[derive(Debug, Clone)]
struct IndexerSummary {
name: String,
file_path: String,
target_index: String,
data_source: String,
skillset: Option<String>,
}
#[derive(Debug, Clone)]
struct SkillEntry {
odata_type: String,
name: Option<String>,
}
#[derive(Debug, Clone)]
struct SkillsetSummary {
name: String,
file_path: String,
skills: Vec<SkillEntry>,
}
#[derive(Debug, Clone)]
struct SynonymMapSummary {
name: String,
file_path: String,
format: String,
}
#[derive(Debug, Clone)]
struct AliasSummary {
name: String,
file_path: String,
indexes: Vec<String>,
}
#[derive(Debug, Clone)]
struct KnowledgeBaseSummary {
name: String,
file_path: String,
description: Option<String>,
retrieval_instructions: Option<String>,
output_mode: Option<String>,
knowledge_sources: Vec<String>,
}
#[derive(Debug, Clone)]
struct KnowledgeSourceSummary {
name: String,
file_path: String,
description: Option<String>,
kind: Option<String>,
index_name: Option<String>,
knowledge_base: Option<String>,
}
#[derive(Debug, Clone)]
struct Dependency {
from: String,
to: String,
kind: String,
}
#[derive(Debug, Clone)]
struct AgentToolSummary {
tool_type: String,
knowledge_base_name: Option<String>,
}
#[derive(Debug, Clone)]
struct AgentSummary {
name: String,
file_path: String,
model: String,
tool_count: usize,
tools: Vec<AgentToolSummary>,
instructions: String,
}
#[derive(Debug, Default)]
struct ProjectSummary {
project_name: String,
search_services: Vec<String>,
foundry_services: Vec<String>,
indexes: Vec<IndexSummary>,
data_sources: Vec<DataSourceSummary>,
indexers: Vec<IndexerSummary>,
skillsets: Vec<SkillsetSummary>,
synonym_maps: Vec<SynonymMapSummary>,
aliases: Vec<AliasSummary>,
knowledge_bases: Vec<KnowledgeBaseSummary>,
knowledge_sources: Vec<KnowledgeSourceSummary>,
agents: Vec<AgentSummary>,
dependencies: Vec<Dependency>,
}
pub async fn run(output: OutputFormat, env_override: Option<&str>) -> Result<()> {
let (project_root, config, env) = load_config_and_env(env_override)?;
let files_root = config.files_root(&project_root);
let include_preview = env.sync.include_preview;
let kinds: Vec<ResourceKind> = if include_preview {
ResourceKind::all().to_vec()
} else {
ResourceKind::stable().to_vec()
};
let mut summary = ProjectSummary {
project_name: config
.project
.name
.clone()
.unwrap_or_else(|| "rigg project".to_string()),
search_services: env.search.iter().map(|s| s.name.clone()).collect(),
foundry_services: env
.foundry
.iter()
.map(|f| format!("{}/{}", f.name, f.project))
.collect(),
..Default::default()
};
for search_svc in &env.search {
let search_base = env.search_service_dir(&files_root, search_svc);
for kind in &kinds {
if kind.domain() != rigg_core::service::ServiceDomain::Search {
continue;
}
let resource_dir = search_base.join(kind.directory_name());
if !resource_dir.exists() {
continue;
}
let values = read_json_files(&resource_dir)?;
match kind {
ResourceKind::Index => {
for (path, v) in &values {
summary.indexes.push(parse_index(path, v));
}
}
ResourceKind::DataSource => {
for (path, v) in &values {
summary.data_sources.push(parse_data_source(path, v));
}
}
ResourceKind::Indexer => {
for (path, v) in &values {
let indexer = parse_indexer(path, v);
add_indexer_dependencies(&indexer, &mut summary.dependencies);
summary.indexers.push(indexer);
}
}
ResourceKind::Skillset => {
for (path, v) in &values {
summary.skillsets.push(parse_skillset(path, v));
}
}
ResourceKind::SynonymMap => {
for (path, v) in &values {
summary.synonym_maps.push(parse_synonym_map(path, v));
}
}
ResourceKind::Alias => {
for (path, v) in &values {
summary.aliases.push(parse_alias(path, v));
}
}
ResourceKind::KnowledgeBase => {
for (path, v) in &values {
summary.knowledge_bases.push(parse_knowledge_base(path, v));
}
}
ResourceKind::KnowledgeSource => {
let ks_values = read_ks_from_dirs(&resource_dir);
for (path, v) in &ks_values {
let ks = parse_knowledge_source(path, v);
add_knowledge_source_dependencies(&ks, &mut summary.dependencies);
summary.knowledge_sources.push(ks);
}
}
ResourceKind::Agent => {}
}
}
}
if env.has_foundry() {
for foundry_config in &env.foundry {
let agents_dir = env
.foundry_service_dir(&files_root, foundry_config)
.join("agents");
if agents_dir.exists() {
if let Ok(entries) = std::fs::read_dir(&agents_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) == Some("yaml") {
if let Some(agent) = parse_agent_yaml(&path) {
summary.agents.push(agent);
}
}
}
}
}
}
}
for agent in &summary.agents {
for tool in &agent.tools {
if let Some(ref kb_name) = tool.knowledge_base_name {
summary.dependencies.push(Dependency {
from: agent.name.clone(),
to: kb_name.clone(),
kind: "Knowledge Base".to_string(),
});
}
}
}
for kb in &summary.knowledge_bases {
for ks_name in &kb.knowledge_sources {
summary.dependencies.push(Dependency {
from: kb.name.clone(),
to: ks_name.clone(),
kind: "Knowledge Source".to_string(),
});
}
}
summary.indexes.sort_by(|a, b| a.name.cmp(&b.name));
summary.data_sources.sort_by(|a, b| a.name.cmp(&b.name));
summary.indexers.sort_by(|a, b| a.name.cmp(&b.name));
summary.skillsets.sort_by(|a, b| a.name.cmp(&b.name));
summary.synonym_maps.sort_by(|a, b| a.name.cmp(&b.name));
summary.aliases.sort_by(|a, b| a.name.cmp(&b.name));
summary.knowledge_bases.sort_by(|a, b| a.name.cmp(&b.name));
summary
.knowledge_sources
.sort_by(|a, b| a.name.cmp(&b.name));
summary.agents.sort_by(|a, b| a.name.cmp(&b.name));
summary.dependencies.sort_by(|a, b| {
a.from
.cmp(&b.from)
.then_with(|| a.kind.cmp(&b.kind))
.then_with(|| a.to.cmp(&b.to))
});
match output {
OutputFormat::Text => text_output::print_text(&summary),
OutputFormat::Json => json_output::print_json(&summary),
}
Ok(())
}
fn read_json_files(dir: &Path) -> Result<Vec<(std::path::PathBuf, Value)>> {
let mut values = Vec::new();
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("json") {
continue;
}
let content = std::fs::read_to_string(&path)?;
let value: Value = serde_json::from_str(&content)?;
values.push((path, value));
}
Ok(values)
}
fn read_ks_from_dirs(ks_base: &Path) -> Vec<(std::path::PathBuf, Value)> {
let mut values = Vec::new();
let entries = match std::fs::read_dir(ks_base) {
Ok(e) => e,
Err(_) => return values,
};
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
let ks_name = match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.to_string(),
None => continue,
};
let ks_file = path.join(format!("{}.json", ks_name));
if let Ok(content) = std::fs::read_to_string(&ks_file) {
if let Ok(value) = serde_json::from_str::<Value>(&content) {
values.push((ks_file, value));
}
}
}
values
}