feat(tools): implement Slash Commands with frontmatter and file refs (M5 complete)
This commit implements the complete M5 milestone (Slash Commands) including: Slash Command Parser (tools-slash): - YAML frontmatter parsing with serde_yaml - Metadata extraction (description, author, tags, version) - Arbitrary frontmatter fields via flattened HashMap - Graceful fallback for commands without frontmatter Argument Substitution: - $ARGUMENTS - all arguments joined by space - $1, $2, $3, etc. - positional arguments - Unmatched placeholders remain unchanged - Empty arguments result in empty string for $ARGUMENTS File Reference Resolution: - @path syntax to include file contents inline - Regex-based matching for file references - Multiple file references supported - Clear error messages for missing files CLI Integration: - Added `slash` subcommand: `owlen slash <command> <args...>` - Loads commands from `.claude/commands/<name>.md` - Permission checks for SlashCommand tool - Automatic file reference resolution before output Command Structure: --- description: "Command description" author: "Author name" tags: - tag1 - tag2 --- Command body with $ARGUMENTS and @file.txt references Permission Enforcement: - Plan mode: SlashCommand allowed (utility tool) - All modes: SlashCommand respects permissions - File references respect filesystem permissions Testing: - 10 tests in tools-slash for parser functionality - Frontmatter parsing with complex YAML - Argument substitution (all variants) - File reference resolution (single and multiple) - Edge cases (no frontmatter, empty args, etc.) - 3 new tests in CLI for integration - slash_command_works (with args and frontmatter) - slash_command_file_refs (file inclusion) - slash_command_not_found (error handling) - All 56 workspace tests passing ✅ Dependencies Added: - serde_yaml 0.9 for YAML frontmatter parsing - regex 1.12 for file reference pattern matching M5 milestone complete! ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ enum Cmd {
|
||||
Write { path: String, content: String },
|
||||
Edit { path: String, old_string: String, new_string: String },
|
||||
Bash { command: String, #[arg(long)] timeout: Option<u64> },
|
||||
Slash { command_name: String, args: Vec<String> },
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -180,6 +181,47 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Cmd::Slash { command_name, args } => {
|
||||
// Check permission
|
||||
match perms.check(Tool::SlashCommand, None) {
|
||||
PermissionDecision::Allow => {
|
||||
// Look for command file in .claude/commands/
|
||||
let command_path = format!(".claude/commands/{}.md", command_name);
|
||||
|
||||
// Read the command file
|
||||
let content = match tools_fs::read_file(&command_path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
return Err(eyre!(
|
||||
"Slash command '{}' not found at {}",
|
||||
command_name,
|
||||
command_path
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Parse with arguments
|
||||
let args_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
|
||||
let slash_cmd = tools_slash::parse_slash_command(&content, &args_refs)?;
|
||||
|
||||
// Resolve file references
|
||||
let resolved_body = slash_cmd.resolve_file_refs()?;
|
||||
|
||||
// Print the resolved command body
|
||||
println!("{}", resolved_body);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
PermissionDecision::Ask => {
|
||||
return Err(eyre!(
|
||||
"Permission denied: Slash command requires approval. Use --mode code to allow."
|
||||
));
|
||||
}
|
||||
PermissionDecision::Deny => {
|
||||
return Err(eyre!("Permission denied: Slash command is blocked."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user