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