Skip to main content

bake_agent_context/agent/context/
mod.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4mod index;
5mod installer;
6mod skill;
7
8pub use index::AgentIndex;
9pub use installer::{ContextFile, ContextPackage, Installer};
10pub use skill::{Skill, install_skills, list_skills};
11
12use bake::{Context, Error, Result};
13
14fn installer(context: &mut Context) -> Result<Installer> {
15    if let Some(installer) = context.get::<Installer>() {
16        return Ok(installer.clone());
17    }
18
19    let installer = Installer::new(context.root())?;
20    context.insert(installer.clone());
21    Ok(installer)
22}
23
24/// List dependency crates that ship context, or list one crate's context files.
25#[bake::task]
26pub fn list(context: &mut Context, package: Option<String>) -> Result<String> {
27    let installer = installer(context)?;
28
29    if let Some(package) = package {
30        let Some(package) = installer.find_package(&package)? else {
31            return Ok(format!("No context found for crate '{package}'"));
32        };
33
34        let files = installer.list_context_files(&package)?;
35        if files.is_empty() {
36            return Ok(format!(
37                "No context files found for crate '{}'.",
38                package.selector()
39            ));
40        }
41        let mut output = format!("Context files for crate '{}':", package.selector());
42        for file in files {
43            output.push_str(&format!("\n  {}", file.path.display()));
44        }
45        return Ok(output);
46    }
47
48    let mut packages = Vec::new();
49    for package in installer.packages() {
50        if !installer.list_context_files(package)?.is_empty() {
51            packages.push(package);
52        }
53    }
54    if packages.is_empty() {
55        return Ok("No dependency context files found".to_owned());
56    }
57
58    let mut output = String::from("Crates with context available:");
59    for package in packages {
60        output.push_str(&format!("\n  {} ({})", package.selector(), package.version));
61    }
62    Ok(output)
63}
64
65/// Show a context file from one resolved dependency crate.
66#[bake::task]
67pub fn show(
68    context: &mut Context,
69    #[bake(named)] package: String,
70    #[bake(named)] file: String,
71) -> Result<String> {
72    let installer = installer(context)?;
73    let Some(content) = installer.show_context_file(&package, &file)? else {
74        return Err(Error::new(format!(
75            "context file {file:?} was not found in crate {package:?}"
76        )));
77    };
78    Ok(content)
79}
80
81/// Install context and skills from one crate or all dependencies, then update agents.md.
82#[bake::task]
83pub fn install(context: &mut Context, package: Option<String>) -> Result<String> {
84    let installer = installer(context)?;
85    let installed_context = if let Some(package) = package.as_deref() {
86        if installer.install_package(package)? {
87            vec![package.to_owned()]
88        } else {
89            Vec::new()
90        }
91    } else {
92        installer.install_all()?
93    };
94    let installed_skills = install_skills(&installer, package.as_deref(), None)?;
95
96    AgentIndex::new(context.root())
97        .with_packages(installer.packages())
98        .update_agents_md("agents.md")?;
99
100    let mut output = Vec::new();
101    if !installed_context.is_empty() {
102        output.push(format!(
103            "Installed context from: {}",
104            installed_context.join(", ")
105        ));
106    }
107    if !installed_skills.is_empty() {
108        output.push(format!("Installed skills: {}", installed_skills.join(", ")));
109    }
110
111    if output.is_empty() {
112        Ok("No dependency context or skills were installed".to_owned())
113    } else {
114        Ok(output.join("\n"))
115    }
116}
117
118/// List skills declared by dependency context documents.
119#[bake::task(name = "agent:context:skill:list")]
120pub fn skill_list(context: &mut Context, package: Option<String>) -> Result<String> {
121    let installer = installer(context)?;
122    let skills = list_skills(&installer, package.as_deref())?;
123    if skills.is_empty() {
124        return Ok("No dependency skills found".to_owned());
125    }
126
127    let mut output = String::from("Available dependency skills:");
128    for skill in skills {
129        output.push_str(&format!(
130            "\n  {} ({}) — {}",
131            skill.name,
132            skill.package_selector(),
133            skill.description
134        ));
135    }
136    Ok(output)
137}
138
139/// Install skills from all dependencies or select a crate and/or package-prefixed skill name.
140#[bake::task(name = "agent:context:skill:install")]
141pub fn skill_install(
142    context: &mut Context,
143    package: Option<String>,
144    skill: Option<String>,
145) -> Result<String> {
146    let installer = installer(context)?;
147    let installed = install_skills(&installer, package.as_deref(), skill.as_deref())?;
148    if installed.is_empty() {
149        Ok("No dependency skills were installed".to_owned())
150    } else {
151        Ok(format!("Installed skills: {}", installed.join(", ")))
152    }
153}
154
155/// Create or update the generated Context section in an agents.md file.
156#[bake::task(name = "agents-md")]
157pub fn agents_md(
158    context: &mut Context,
159    #[bake(default = "agents.md")] path: String,
160) -> Result<String> {
161    let installer = installer(context)?;
162    AgentIndex::new(context.root())
163        .with_packages(installer.packages())
164        .update_agents_md(&path)?;
165    Ok(format!("Updated {path}"))
166}