1use std::path::{Path, PathBuf};
9
10use crate::Error;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct SkillDocument {
15 pub name: &'static str,
17 pub title: &'static str,
19 pub body: &'static str,
21}
22
23macro_rules! document {
24 ($name:literal, $title:literal) => {
25 SkillDocument {
26 name: $name,
27 title: $title,
28 body: include_str!(concat!("../skill/", $name)),
29 }
30 };
31}
32
33pub const DOCUMENTS: &[SkillDocument] = &[
35 document!("SKILL.md", "How to drive xtalforge"),
36 document!("mcp.md", "MCP client setup"),
37 document!("prompts/build-mof.md", "Prompt: build a MOF"),
38 document!("prompts/porosity-report.md", "Prompt: porosity report"),
39 document!(
40 "prompts/prepare-deposited-cif.md",
41 "Prompt: prepare a deposited CIF"
42 ),
43 document!("references/api.md", "Reference: API and surfaces"),
44 document!("references/calculations.md", "Reference: calculations"),
45 document!("references/carbon.md", "Reference: carbon builders"),
46 document!("references/diagnostics.md", "Reference: diagnostics"),
47 document!("references/mof.md", "Reference: MOF builder"),
48 document!("references/polymer.md", "Reference: polymers"),
49 document!("references/porosity.md", "Reference: porosity"),
50 document!("references/prepare.md", "Reference: preparation"),
51 document!(
52 "references/workspace.md",
53 "Reference: documents and history"
54 ),
55];
56
57#[must_use]
59pub fn find(name: &str) -> Option<&'static SkillDocument> {
60 let wanted = name.trim().trim_start_matches('/');
61 DOCUMENTS.iter().find(|document| {
62 document.name == wanted
63 || document
64 .name
65 .strip_suffix(".md")
66 .is_some_and(|stem| stem == wanted)
67 })
68}
69
70#[must_use]
73pub fn default_directory() -> PathBuf {
74 if let Some(base) = std::env::var_os("XDG_DATA_HOME")
75 && !base.is_empty()
76 {
77 return PathBuf::from(base).join("xtalforge").join("skill");
78 }
79 if let Some(home) = std::env::var_os("HOME")
80 && !home.is_empty()
81 {
82 return PathBuf::from(home)
83 .join(".local")
84 .join("share")
85 .join("xtalforge")
86 .join("skill");
87 }
88 PathBuf::from("xtalforge-skill")
89}
90
91pub fn install(directory: &Path) -> Result<Vec<PathBuf>, Error> {
98 let mut written = Vec::with_capacity(DOCUMENTS.len());
99 for document in DOCUMENTS {
100 let path = directory.join(document.name);
101 if let Some(parent) = path.parent() {
102 std::fs::create_dir_all(parent)
103 .map_err(|error| Error::Io(format!("{}: {error}", parent.display())))?;
104 }
105 std::fs::write(&path, document.body)
106 .map_err(|error| Error::Io(format!("{}: {error}", path.display())))?;
107 written.push(path);
108 }
109 Ok(written)
110}