use crate::config::YamlConfig;
use crate::constants::section;
use rust_embed::RustEmbed;
use serde::Deserialize;
use std::borrow::Cow;
use std::fs;
#[derive(RustEmbed)]
#[folder = "assets/"]
#[exclude = "remote/node_modules/*"]
#[exclude = "remote/dist/*"]
pub struct Assets;
pub fn help_text() -> Cow<'static, str> {
Assets::get("help.md")
.map(|f| String::from_utf8_lossy(&f.data).into_owned().into())
.unwrap_or_else(|| Cow::Borrowed(""))
}
pub fn version_template() -> Cow<'static, str> {
Assets::get("version.md")
.map(|f| String::from_utf8_lossy(&f.data).into_owned().into())
.unwrap_or_else(|| Cow::Borrowed(""))
}
pub fn default_system_prompt() -> Cow<'static, str> {
Assets::get("system_prompt_default.md")
.map(|f| String::from_utf8_lossy(&f.data).into_owned().into())
.unwrap_or_else(|| Cow::Borrowed(""))
}
pub fn default_memory() -> Cow<'static, str> {
Assets::get("memory_default.md")
.map(|f| String::from_utf8_lossy(&f.data).into_owned().into())
.unwrap_or_else(|| Cow::Borrowed(""))
}
pub fn default_soul() -> Cow<'static, str> {
Assets::get("soul_default.md")
.map(|f| String::from_utf8_lossy(&f.data).into_owned().into())
.unwrap_or_else(|| Cow::Borrowed(""))
}
pub fn quotes_text() -> &'static str {
include_str!("../assets/quotes.txt")
}
pub fn install_default_skills(skills_dir: &std::path::Path) -> Result<(), std::io::Error> {
for filename in Assets::iter() {
let filename = filename.as_ref();
if !filename.starts_with("skills/") {
continue;
}
let rel_path = &filename["skills/".len()..];
if rel_path.is_empty() {
continue;
}
let dst_path = skills_dir.join(rel_path);
if dst_path.exists() {
continue;
}
let asset = Assets::get(filename).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("asset not found: {}", filename),
)
})?;
if let Some(parent) = dst_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&dst_path, asset.data)?;
}
Ok(())
}
pub fn install_default_commands(commands_dir: &std::path::Path) -> Result<(), std::io::Error> {
for filename in Assets::iter() {
let filename = filename.as_ref();
if !filename.starts_with("commands/") {
continue;
}
let rel_path = &filename["commands/".len()..];
if rel_path.is_empty() {
continue;
}
let dst_path = commands_dir.join(rel_path);
if dst_path.exists() {
continue;
}
let asset = Assets::get(filename).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("asset not found: {}", filename),
)
})?;
if let Some(parent) = dst_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&dst_path, asset.data)?;
}
Ok(())
}
#[derive(Deserialize)]
struct PresetEntry {
name: String,
file: String,
}
pub fn install_default_scripts(config: &mut YamlConfig) -> Result<(), Box<dyn std::error::Error>> {
let manifest_asset = Assets::get("presets/manifest.yaml")
.ok_or("presets/manifest.yaml not found in embedded assets")?;
let manifest_str = std::str::from_utf8(&manifest_asset.data)?;
let entries: Vec<PresetEntry> = serde_yaml::from_str(manifest_str)?;
let scripts_dir = YamlConfig::scripts_dir();
for entry in &entries {
let dst_path = scripts_dir.join(&entry.file);
if dst_path.exists() {
continue;
}
let asset_path = format!("presets/{}", entry.file);
let asset = Assets::get(&asset_path)
.ok_or_else(|| format!("preset script not found in embedded assets: {}", asset_path))?;
if let Some(parent) = dst_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&dst_path, &asset.data)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o755);
fs::set_permissions(&dst_path, perms)?;
}
let path_str = dst_path.to_string_lossy().to_string();
config.set_property(section::PATH, &entry.name, &path_str);
config.set_property(section::SCRIPT, &entry.name, &path_str);
}
Ok(())
}