battlecommand_forge/
custom_commands.rs1use anyhow::Result;
16use std::path::Path;
17
18#[derive(Debug, Clone)]
19pub struct CustomCommand {
20 pub name: String,
21 pub description: String,
22 pub model: Option<String>,
23 pub prompt: String,
24}
25
26pub async fn load_commands(workspace: &str) -> Result<Vec<CustomCommand>> {
28 let dir = format!("{}/.battlecommand/commands", workspace);
29 if !Path::new(&dir).exists() {
30 return Ok(Vec::new());
31 }
32
33 let mut commands = Vec::new();
34 let mut entries = tokio::fs::read_dir(&dir).await?;
35
36 while let Some(entry) = entries.next_entry().await? {
37 let path = entry.path();
38 if path.extension().map(|e| e == "md").unwrap_or(false) {
39 if let Ok(content) = tokio::fs::read_to_string(&path).await {
40 if let Some(cmd) = parse_command_file(&path, &content) {
41 commands.push(cmd);
42 }
43 }
44 }
45 }
46
47 commands.sort_by(|a, b| a.name.cmp(&b.name));
48 Ok(commands)
49}
50
51fn parse_command_file(path: &Path, content: &str) -> Option<CustomCommand> {
52 let name = path.file_stem()?.to_str()?.to_string();
53 let mut description = String::new();
54 let mut model = None;
55 let mut prompt = String::new();
56 let mut in_prompt = false;
57
58 for line in content.lines() {
59 if line.starts_with("Description:") {
60 description = line.trim_start_matches("Description:").trim().to_string();
61 } else if line.starts_with("Model:") {
62 model = Some(line.trim_start_matches("Model:").trim().to_string());
63 } else if line.trim() == "## Prompt" {
64 in_prompt = true;
65 } else if in_prompt {
66 prompt.push_str(line);
67 prompt.push('\n');
68 }
69 }
70
71 if prompt.is_empty() {
72 prompt = content.to_string();
73 }
74
75 Some(CustomCommand {
76 name,
77 description,
78 model,
79 prompt: prompt.trim().to_string(),
80 })
81}
82
83pub async fn create_example_command(workspace: &str) -> Result<()> {
85 let dir = format!("{}/.battlecommand/commands", workspace);
86 tokio::fs::create_dir_all(&dir).await?;
87
88 let example = r#"# Review
89Description: Run a comprehensive code review on the project
90Model: qwen2.5-coder:7b
91
92## Prompt
93Review all Python files in the tasks/ directory. For each file:
941. Check code quality and style
952. Identify potential bugs
963. Suggest improvements
974. Rate each file 1-10
98
99Output a summary table with file names and scores.
100"#;
101
102 tokio::fs::write(format!("{}/review.md", dir), example).await?;
103 println!("Created example command: .battlecommand/commands/review.md");
104 Ok(())
105}
106
107pub fn format_commands_help(commands: &[CustomCommand]) -> String {
109 if commands.is_empty() {
110 return "No custom commands. Create .battlecommand/commands/<name>.md to add one."
111 .to_string();
112 }
113 let mut output = String::from("Custom Commands:\n");
114 for cmd in commands {
115 output.push_str(&format!(
116 " /{:<15} {}\n",
117 cmd.name,
118 if cmd.description.is_empty() {
119 "(no description)"
120 } else {
121 &cmd.description
122 }
123 ));
124 }
125 output
126}