pub mod allowlist;
pub mod clawhub;
pub mod crystallizer;
pub mod loader;
pub mod manifest;
pub mod registry;
pub mod runner;
pub mod stats;
pub use stats::{record_skill_use, retire_unused_auto_skills};
pub mod sig;
pub mod workflow_distill;
pub use clawhub::{ClawhubClient, LockFile, LockedSkill, SearchResult, SkillSource};
pub use loader::{SkillRegistry, default_global_skills_dir, load_skills};
pub use manifest::{SkillManifest, ToolSpec, parse_skill_md};
pub use runner::{RunOptions, run_tool};
pub fn valid_slug(s: &str) -> bool {
let b = s.as_bytes();
if b.is_empty() || b.len() > 128 {
return false;
}
if !(b[0].is_ascii_lowercase() || b[0].is_ascii_digit()) {
return false;
}
b.iter()
.all(|&c| c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, b'.' | b'-' | b'_'))
}
#[cfg(test)]
mod tests {
use super::valid_slug;
#[test]
fn slug_accepts_normal_names() {
assert!(valid_slug("hithink-market-query"));
assert!(valid_slug("a"));
assert!(valid_slug("skill_1.2"));
assert!(valid_slug("brave-search"));
}
#[test]
fn slug_rejects_traversal_and_separators() {
assert!(!valid_slug(""), "empty");
assert!(!valid_slug(".."), "dotdot");
assert!(!valid_slug("../../.ssh"), "traversal");
assert!(!valid_slug("foo/bar"), "slash");
assert!(!valid_slug("/etc/passwd"), "absolute");
assert!(!valid_slug(".hidden"), "leading dot");
assert!(!valid_slug("UPPER"), "uppercase");
assert!(!valid_slug("a b"), "space");
assert!(!valid_slug(&"x".repeat(129)), "too long");
}
}