pub mod cli;
pub mod discovery;
pub mod embedded;
pub mod installer;
pub mod lock;
pub mod providers;
pub mod types;
pub use cli::{get_command_schema, get_commands, output_commands_json};
pub use discovery::{discover_skills, discover_skills_with_provider, DiscoveryConfig};
pub use embedded::{get_embedded_skill, register_embedded_skill};
pub use installer::{
install_skill, install_skill_with_provider, InstallConfig, InstallMode, InstallResult,
};
pub use lock::LockManager;
pub use providers::{MockProvider, SkillProvider};
pub use types::{Skill, SkillLock, Source, SourceType};
#[cfg(test)]
mod integration_tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_end_to_end_embedded_install() {
let temp_dir = TempDir::new().unwrap();
let canonical_dir = temp_dir.path().join(".agents/skills");
let lock_path = temp_dir.path().join(".agents/.skill-lock.json");
let source = Source {
source_type: SourceType::Self_,
url: None,
subpath: None,
skill_filter: None,
ref_: None,
};
let config = DiscoveryConfig::default();
let skills = discover_skills(&source, &config).unwrap();
assert_eq!(skills.len(), 1);
let skill = &skills[0];
let install_config = InstallConfig::new(canonical_dir.clone());
let result = install_skill(skill, &install_config).unwrap();
assert!(result.path.exists());
assert!(result.path.join("SKILL.md").exists());
let lock_manager = LockManager::new(lock_path);
lock_manager
.update_entry(&skill.name, &source, &result.path)
.unwrap();
let entry = lock_manager.get_entry(&skill.name).unwrap().unwrap();
assert_eq!(entry.source_type, "self");
assert!(!entry.skill_folder_hash.is_empty());
}
#[test]
fn test_self_and_embedded_are_equivalent() {
let config = DiscoveryConfig::default();
let json_self = r#"{"type":"self"}"#;
let source_self: Source = serde_json::from_str(json_self).unwrap();
let skills_self = discover_skills(&source_self, &config).unwrap();
let json_embedded = r#"{"type":"embedded"}"#;
let source_embedded: Source = serde_json::from_str(json_embedded).unwrap();
let skills_embedded = discover_skills(&source_embedded, &config).unwrap();
assert_eq!(skills_self.len(), skills_embedded.len());
assert_eq!(skills_self[0].name, skills_embedded[0].name);
assert_eq!(source_self.source_type, source_embedded.source_type);
}
#[test]
fn test_no_external_calls_for_embedded() {
let temp_dir = TempDir::new().unwrap();
let canonical_dir = temp_dir.path().join(".agents/skills");
let lock_path = temp_dir.path().join(".agents/.skill-lock.json");
let source = Source {
source_type: SourceType::Self_,
url: None,
subpath: None,
skill_filter: None,
ref_: None,
};
let config = DiscoveryConfig::default();
let skills = discover_skills(&source, &config).unwrap();
let install_config = InstallConfig::new(canonical_dir.clone());
let result = install_skill(&skills[0], &install_config).unwrap();
let lock_manager = LockManager::new(lock_path);
lock_manager
.update_entry(&skills[0].name, &source, &result.path)
.unwrap();
assert!(result.path.exists());
}
#[test]
fn test_end_to_end_embedded_install_with_aux_files() {
use std::collections::HashMap;
use types::SkillMetadata;
let temp_dir = TempDir::new().unwrap();
let canonical_dir = temp_dir.path().join(".agents/skills");
let mut auxiliary_files = HashMap::new();
auxiliary_files.insert(
"scripts/run.py".to_string(),
"#!/usr/bin/env python3\nprint('running')".to_string(),
);
auxiliary_files.insert(
"references/overview.md".to_string(),
"# Overview\nSkill overview.".to_string(),
);
let skill = Skill {
name: "embedded-multi-skill".to_string(),
description: "Multi-file embedded skill".to_string(),
path: None,
raw_content:
"---\nname: embedded-multi-skill\ndescription: Multi-file embedded skill\n---\n\n# Skill"
.to_string(),
metadata: SkillMetadata::default(),
auxiliary_files,
};
let install_config = InstallConfig::new(canonical_dir.clone());
let result = install_skill(&skill, &install_config).unwrap();
assert!(result.path.join("SKILL.md").exists());
let skill_md = std::fs::read_to_string(result.path.join("SKILL.md")).unwrap();
assert_eq!(skill_md, skill.raw_content);
assert!(result.path.join("scripts/run.py").exists());
let run_py = std::fs::read_to_string(result.path.join("scripts/run.py")).unwrap();
assert_eq!(run_py, "#!/usr/bin/env python3\nprint('running')");
assert!(result.path.join("references/overview.md").exists());
let overview = std::fs::read_to_string(result.path.join("references/overview.md")).unwrap();
assert_eq!(overview, "# Overview\nSkill overview.");
}
#[test]
fn test_github_flow_with_mock_provider() {
use types::SkillMetadata;
let temp_dir = TempDir::new().unwrap();
let canonical_dir = temp_dir.path().join(".agents/skills");
let lock_path = temp_dir.path().join(".agents/.skill-lock.json");
let github_skill = Skill {
name: "github-test-skill".to_string(),
description: "A test skill from GitHub".to_string(),
path: None,
raw_content: r#"---
name: github-test-skill
description: A test skill from GitHub
---
# GitHub Test Skill
This is a test skill.
"#
.to_string(),
metadata: SkillMetadata::default(),
auxiliary_files: Default::default(),
};
let provider = MockProvider::new(vec![github_skill.clone()])
.with_hash("github-hash-abc123".to_string());
let source = Source {
source_type: SourceType::Github,
url: Some("https://github.com/example/skills".to_string()),
subpath: None,
skill_filter: None,
ref_: None,
};
let config = DiscoveryConfig::default();
let skills = discover_skills_with_provider(&source, &config, Some(&provider)).unwrap();
assert_eq!(skills.len(), 1);
assert_eq!(skills[0].name, "github-test-skill");
let install_config = InstallConfig::new(canonical_dir.clone());
let result =
install_skill_with_provider(&skills[0], &install_config, Some(&provider)).unwrap();
assert!(result.path.exists());
assert!(result.path.join("SKILL.md").exists());
let lock_manager = LockManager::new(lock_path.clone());
lock_manager
.update_entry_with_hash(
&skills[0].name,
&source,
&result.path,
"github-hash-abc123".to_string(),
)
.unwrap();
let entry = lock_manager.get_entry(&skills[0].name).unwrap().unwrap();
assert_eq!(entry.source_type, "github");
assert_eq!(
entry.source_url,
Some("https://github.com/example/skills".to_string())
);
assert_eq!(entry.skill_folder_hash, "github-hash-abc123");
let json_content = std::fs::read_to_string(&lock_path).unwrap();
assert!(json_content.contains("sourceType"));
assert!(json_content.contains("sourceUrl"));
assert!(json_content.contains("skillPath"));
assert!(json_content.contains("skillFolderHash"));
assert!(json_content.contains("installedAt"));
assert!(json_content.contains("updatedAt"));
assert!(!json_content.contains("source_type"));
assert!(!json_content.contains("source_url"));
assert!(!json_content.contains("skill_path"));
assert!(!json_content.contains("skill_folder_hash"));
assert!(!json_content.contains("installed_at"));
assert!(!json_content.contains("updated_at"));
}
#[test]
fn test_github_cli_integration() {
use types::SkillMetadata;
let temp_dir = TempDir::new().unwrap();
let source = Source {
source_type: SourceType::Github,
url: Some("https://github.com/mock/skills".to_string()),
subpath: None,
skill_filter: None,
ref_: None,
};
let mock_skill = Skill {
name: "cli-github-skill".to_string(),
description: "CLI GitHub skill".to_string(),
path: None,
raw_content: r#"---
name: cli-github-skill
description: CLI GitHub skill
---
# CLI GitHub Skill
"#
.to_string(),
metadata: SkillMetadata::default(),
auxiliary_files: Default::default(),
};
let provider = MockProvider::new(vec![mock_skill]).with_hash("cli-mock-hash".to_string());
let config = DiscoveryConfig::default();
let skills = discover_skills_with_provider(&source, &config, Some(&provider)).unwrap();
assert_eq!(skills.len(), 1);
let canonical_dir = temp_dir.path().join(".agents/skills");
let install_config = InstallConfig::new(canonical_dir);
let result =
install_skill_with_provider(&skills[0], &install_config, Some(&provider)).unwrap();
let lock_path = temp_dir.path().join(".agents/.skill-lock.json");
let lock_manager = LockManager::new(lock_path.clone());
let hash = provider.get_folder_hash(&skills[0]).unwrap();
lock_manager
.update_entry_with_hash(&skills[0].name, &source, &result.path, hash)
.unwrap();
let entry = lock_manager.get_entry(&skills[0].name).unwrap().unwrap();
assert_eq!(entry.source_type, "github");
assert_eq!(entry.skill_folder_hash, "cli-mock-hash");
assert!(entry.source_url.is_some());
}
}