use crate::{core::settings_manager::agent_dir, embedded};
use std::path::PathBuf;
const DOCS: &[(&str, &str)] = &[
("index.md", embedded!("docs/index.md")),
("quickstart.md", embedded!("docs/quickstart.md")),
("settings.md", embedded!("docs/settings.md")),
("keybindings.md", embedded!("docs/keybindings.md")),
("models.md", embedded!("docs/models.md")),
(
"environment-variables.md",
embedded!("docs/environment-variables.md"),
),
("http-proxy.md", embedded!("docs/http-proxy.md")),
("themes.md", embedded!("docs/themes.md")),
("skills.md", embedded!("docs/skills.md")),
("prompt-templates.md", embedded!("docs/prompt-templates.md")),
("tui.md", embedded!("docs/tui.md")),
("extensions.md", embedded!("docs/extensions.md")),
("virtual-models.md", embedded!("docs/virtual-models.md")),
(
"extensions/subagent.md",
embedded!("docs/extensions/subagent.md"),
),
(
"extensions/codemode.md",
embedded!("docs/extensions/codemode.md"),
),
("extensions/mcp.md", embedded!("docs/extensions/mcp.md")),
(
"extensions/permissions.md",
embedded!("docs/extensions/permissions.md"),
),
("extensions/vet.md", embedded!("docs/extensions/vet.md")),
(
"extensions/computer.md",
embedded!("docs/extensions/computer.md"),
),
("custom-provider.md", embedded!("docs/custom-provider.md")),
("sessions.md", embedded!("docs/sessions.md")),
("compaction.md", embedded!("docs/compaction.md")),
("sdk.md", embedded!("docs/sdk.md")),
];
pub fn docs_dir() -> PathBuf {
agent_dir().join("docs")
}
pub fn sync_docs() -> (usize, usize) {
let mut written = 0;
let mut skipped = 0;
let dir = docs_dir();
_ = std::fs::create_dir_all(&dir);
for (file, content) in DOCS {
let path = dir.join(file);
if let Some(parent) = path.parent() {
_ = std::fs::create_dir_all(parent);
}
match std::fs::read_to_string(&path) {
Ok(existing) if existing == *content => skipped += 1,
_ => {
if std::fs::write(&path, content).is_ok() {
written += 1;
}
}
}
}
(written, skipped)
}
pub fn ensure_docs() {
let (written, _) = sync_docs();
if written > 0 {
eprintln!(
"doc-sync: wrote {written} bundled doc(s) into {}",
docs_dir().display()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn setup() -> (crate::test_support::AgentDirGuard, std::path::PathBuf) {
let ad = crate::test_support::AgentDirGuard::temp();
let docs = crate::core::settings_manager::agent_dir().join("docs");
let _ = std::fs::remove_dir_all(&docs);
(ad, docs)
}
#[test]
fn writes_all_and_is_idempotent() {
let (_g, docs) = setup();
let (written, skipped) = sync_docs();
assert_eq!(written, DOCS.len());
assert_eq!(skipped, 0);
for (file, content) in DOCS {
assert_eq!(
std::fs::read_to_string(docs.join(file)).unwrap(),
*content,
"{}",
file
);
}
let (written2, skipped2) = sync_docs();
assert_eq!(written2, 0);
assert_eq!(skipped2, DOCS.len());
}
#[test]
fn overwrites_modified_file_and_recreates_deleted() {
let (_g, docs) = setup();
sync_docs();
let keybindings = docs.join("keybindings.md");
std::fs::write(&keybindings, "外部篡改内容").unwrap();
let (written, _) = sync_docs();
assert!(written >= 1);
assert_eq!(
std::fs::read_to_string(&keybindings).unwrap(),
*DOCS.iter().find(|(f, _)| *f == "keybindings.md").unwrap().1,
);
let models = docs.join("models.md");
std::fs::remove_file(&models).unwrap();
sync_docs();
assert!(models.exists());
}
#[test]
fn preserves_files_not_in_manifest() {
let (_g, docs) = setup();
sync_docs();
let extra = docs.join("user-notes.md");
std::fs::write(&extra, "# 用户自己的笔记").unwrap();
sync_docs();
assert_eq!(std::fs::read_to_string(&extra).unwrap(), "# 用户自己的笔记");
}
}