API Guide
This guide provides comprehensive usage patterns and examples for the SwissArmyHammer Rust API.
Overview
SwissArmyHammer provides a rich Rust API for programmatic prompt management, template rendering, workflow orchestration, and memoranda handling. The API is designed with both synchronous and asynchronous patterns in mind.
Core Components
- PromptLibrary - Main interface for prompt management
- Prompt - Individual prompt representation
- PromptResolver - Advanced prompt loading and resolution
- TemplateEngine - Low-level template rendering
- Workflows - State-based execution workflows
- Memoranda - Structured note management
- Storage - Pluggable storage backends
Quick Start
use swissarmyhammer::{PromptLibrary, ArgumentSpec, Prompt};
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a library and load prompts
let mut library = PromptLibrary::new();
library.add_directory("./.swissarmyhammer/prompts")?;
// Use a prompt
let prompt = library.get("code-review")?;
let mut args = HashMap::new();
args.insert("language".to_string(), "rust".to_string());
let result = prompt.render(&args)?;
println!("{}", result);
Ok(())
}
PromptLibrary
The PromptLibrary
is the primary interface for managing collections of prompts.
Creation and Initialization
use swissarmyhammer::PromptLibrary;
// Create an empty library
let mut library = PromptLibrary::new();
// Load from directories (common pattern)
if std::path::Path::new("./.swissarmyhammer/prompts").exists() {
let count = library.add_directory("./.swissarmyhammer/prompts")?;
println!("Loaded {} prompts", count);
}
// Load from multiple sources
library.add_directory("./global/prompts")?;
library.add_directory("./project/prompts")?;
Adding Prompts Programmatically
use swissarmyhammer::{Prompt, ArgumentSpec};
let prompt = Prompt::new("greeting", "Hello {{name}}!")
.with_description("A simple greeting")
.add_argument(ArgumentSpec {
name: "name".to_string(),
description: Some("Person's name".to_string()),
required: true,
default: Some("World".to_string()),
type_hint: Some("string".to_string()),
});
library.add(prompt)?;
Retrieving and Using Prompts
use std::collections::HashMap;
// Get by name
let prompt = library.get("greeting")?;
// Render with arguments
let mut args = HashMap::new();
args.insert("name".to_string(), "Alice".to_string());
let result = prompt.render(&args)?;
Listing and Discovery
// List all prompts
for prompt in library.list()? {
println!("{}: {}", prompt.name,
prompt.description.as_deref().unwrap_or("No description"));
}
// Filter by category
for prompt in library.list()? {
if prompt.category.as_deref() == Some("development") {
println!("Dev prompt: {}", prompt.name);
}
}
// Search by content
let matches = library.search("code review")?;
for prompt in matches {
println!("Found: {}", prompt.name);
}
Prompt
Individual prompts encapsulate template content and metadata.
Creating Prompts
use swissarmyhammer::{Prompt, ArgumentSpec};
let prompt = Prompt::new("code-review", r#"
Review this {{language}} code:
```{{language}}
{{code}}
Focus on:
- Best practices
- Potential bugs
- Performance “#) .with_description(“Comprehensive code review prompt”) .with_category(“development”) .with_tags(vec![“code”.to_string(), “review”.to_string()]) .add_argument(ArgumentSpec { name: “language”.to_string(), description: Some(“Programming language”.to_string()), required: true, default: None, type_hint: Some(“string”.to_string()), }) .add_argument(ArgumentSpec { name: “code”.to_string(), description: Some(“Code to review”.to_string()), required: true, default: None, type_hint: Some(“text”.to_string()), });
### Argument Validation
```rust
// Check required arguments
if let Err(missing) = prompt.validate_arguments(&args) {
eprintln!("Missing arguments: {:?}", missing);
return Ok(());
}
// Get argument specifications
for arg in &prompt.arguments {
println!("Arg: {} (required: {})", arg.name, arg.required);
if let Some(default) = &arg.default {
println!(" Default: {}", default);
}
}
Template Features
// Basic variable substitution
let template = "Hello {{name}}!";
// Conditionals
let template = r#"
{% if urgent %}
**URGENT**: {{message}}
{% else %}
{{message}}
{% endif %}
"#;
// Loops
let template = r#"
{% for item in items %}
- {{item.name}}: {{item.description}}
{% endfor %}
"#;
// Custom filters (if registered)
let template = "{{text | upper | truncate: 50}}";
PromptResolver
For advanced prompt loading and resolution scenarios.
Basic Usage
use swissarmyhammer::PromptResolver;
let resolver = PromptResolver::new();
// Get all available prompts from standard locations
let prompts = resolver.resolve_all()?;
// Resolve specific prompt by name
if let Some(prompt) = resolver.resolve("code-review")? {
println!("Found prompt: {}", prompt.name);
}
Custom Search Paths
use swissarmyhammer::{PromptResolver, FileSource};
let mut resolver = PromptResolver::new();
resolver.add_source(FileSource::from_directory("./custom/prompts")?);
// Resolve from custom sources
let prompts = resolver.resolve_all()?;
Source Priority
// Sources are resolved in order, later sources override earlier ones
resolver.add_source(FileSource::from_directory("./global")?); // Lower priority
resolver.add_source(FileSource::from_directory("./project")?); // Medium priority
resolver.add_source(FileSource::from_directory("./local")?); // Higher priority
// "code-review" from ./local will override ./project and ./global
let prompt = resolver.resolve("code-review")?;
TemplateEngine
Low-level template rendering with custom filters and context.
Basic Rendering
use swissarmyhammer::TemplateEngine;
use std::collections::HashMap;
let engine = TemplateEngine::new();
let template = engine.parse("Hello {{name}}!")?;
let mut context = HashMap::new();
context.insert("name".to_string(), "World".to_string());
let result = template.render(&context)?;
Custom Filters
use swissarmyhammer::{TemplateEngine, CustomLiquidFilter};
use liquid::ValueView;
struct UppercaseFilter;
impl CustomLiquidFilter for UppercaseFilter {
fn name(&self) -> &'static str {
"upper"
}
fn filter(&self, input: &dyn ValueView) -> Result<String, Box<dyn std::error::Error>> {
Ok(input.to_kstr().to_uppercase())
}
}
let mut engine = TemplateEngine::new();
engine.register_filter(Box::new(UppercaseFilter))?;
let template = engine.parse("{{name | upper}}")?;
// Renders: "ALICE" from input "alice"
Advanced Context
use serde_json::json;
let context = json!({
"user": {
"name": "Alice",
"role": "developer"
},
"items": [
{"name": "Task 1", "done": true},
{"name": "Task 2", "done": false}
]
});
let template = r#"
User: {{user.name}} ({{user.role}})
Pending tasks:
{% for item in items %}
{% unless item.done %}
- {{item.name}}
{% endunless %}
{% endfor %}
"#;
Workflows
State-based execution for complex multi-step processes.
Defining Workflows
use swissarmyhammer::{Workflow, State, Transition};
let workflow = Workflow::new("code-review-process")
.with_description("Complete code review workflow")
.add_state(State::new("start")
.with_prompt("code-review-request"))
.add_state(State::new("review")
.with_prompt("perform-code-review"))
.add_state(State::new("feedback")
.with_prompt("format-feedback"))
.add_state(State::new("complete"))
.add_transition(Transition::new("start", "review")
.with_condition("review_requested"))
.add_transition(Transition::new("review", "feedback")
.with_condition("review_complete"))
.add_transition(Transition::new("feedback", "complete"));
Executing Workflows
use std::collections::HashMap;
// Start a workflow run
let mut context = HashMap::new();
context.insert("code".to_string(), "fn main() {}".to_string());
context.insert("language".to_string(), "rust".to_string());
let mut run = workflow.start_run(context)?;
// Execute step by step
while !run.is_complete() {
let current_state = run.current_state();
println!("Current state: {:?}", current_state);
// Get the prompt for this state
if let Some(prompt_name) = current_state.prompt_name() {
let prompt = library.get(prompt_name)?;
let result = prompt.render(run.context())?;
// Process result and advance
run.set_variable("result", result);
run.advance("next")?;
}
}
let final_result = run.get_variable("result");
Workflow Persistence
use swissarmyhammer::{WorkflowRun, WorkflowRunStatus};
// Save workflow state
let run_id = run.id();
let status = run.status(); // InProgress, Completed, Failed
let state_data = run.serialize()?;
// Later, restore workflow
let restored_run = WorkflowRun::deserialize(&state_data)?;
Memoranda
Structured note and memo management.
Creating Memos
use swissarmyhammer::{Memo, CreateMemoRequest};
let memo_request = CreateMemoRequest {
title: "Meeting Notes".to_string(),
content: r#"
Team Meeting 2024-01-15
# Attendees
- Alice, Bob, Charlie
# Action Items
- [ ] Review PR #123
- [ ] Update documentation
"#.to_string(),
};
// This would typically be used with MCP or storage layer
Searching Memos
use swissarmyhammer::SearchMemosRequest;
let search_request = SearchMemosRequest {
query: "meeting action items".to_string(),
};
// Returns SearchMemosResponse with matching memos
// Implementation depends on storage backend
Storage
Pluggable storage backends for prompts and data.
File System Storage
use swissarmyhammer::{PromptStorage, StorageBackend};
use std::path::PathBuf;
// Default file system storage
let storage = PromptStorage::new_filesystem(PathBuf::from("./.swissarmyhammer"))?;
// Store and retrieve prompts
storage.store_prompt(&prompt)?;
let retrieved = storage.get_prompt("greeting")?;
// List all stored prompts
let all_prompts = storage.list_prompts()?;
Custom Storage Backend
use swissarmyhammer::{StorageBackend, Prompt};
use async_trait::async_trait;
struct DatabaseStorage {
connection: DatabaseConnection,
}
#[async_trait]
impl StorageBackend for DatabaseStorage {
async fn store_prompt(&self, prompt: &Prompt) -> Result<(), SwissArmyHammerError> {
// Store in database
Ok(())
}
async fn get_prompt(&self, name: &str) -> Result<Option<Prompt>, SwissArmyHammerError> {
// Retrieve from database
Ok(None)
}
async fn list_prompts(&self) -> Result<Vec<Prompt>, SwissArmyHammerError> {
// List all prompts
Ok(vec![])
}
}
Error Handling
SwissArmyHammer uses a comprehensive error system.
Error Types
use swissarmyhammer::{SwissArmyHammerError, Result};
fn handle_errors() -> Result<()> {
match library.get("nonexistent") {
Ok(prompt) => println!("Found: {}", prompt.name),
Err(SwissArmyHammerError::PromptNotFound(name)) => {
eprintln!("Prompt '{}' not found", name);
}
Err(SwissArmyHammerError::Template(msg)) => {
eprintln!("Template error: {}", msg);
}
Err(SwissArmyHammerError::Io(err)) => {
eprintln!("IO error: {}", err);
}
Err(err) => {
eprintln!("Other error: {}", err);
}
}
Ok(())
}
Result Chaining
// Chain operations safely
let result = library
.get("code-review")?
.render(&args)?;
// Or with explicit error handling
let prompt = library.get("code-review").map_err(|e| {
eprintln!("Failed to get prompt: {}", e);
e
})?;
Advanced Patterns
Plugin System
use swissarmyhammer::{SwissArmyHammerPlugin, PluginRegistry};
struct CustomPlugin;
impl SwissArmyHammerPlugin for CustomPlugin {
fn name(&self) -> &'static str {
"custom-plugin"
}
fn initialize(&self, registry: &mut PluginRegistry) -> Result<()> {
// Register custom filters, templates, etc.
Ok(())
}
}
let mut registry = PluginRegistry::new();
registry.register(Box::new(CustomPlugin))?;
Configuration Management
use swissarmyhammer::Config;
let config = Config::builder()
.prompt_directories(vec!["./prompts", "./shared/prompts"])
.template_cache_size(1000)
.enable_file_watching(true)
.build()?;
let library = PromptLibrary::with_config(config)?;
Async Patterns
use swissarmyhammer::PromptResolver;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resolver = PromptResolver::new();
// Async prompt resolution
let prompts = resolver.resolve_all_async().await?;
// Concurrent prompt rendering
let tasks: Vec<_> = prompts.into_iter()
.map(|prompt| {
let args = args.clone();
tokio::spawn(async move {
prompt.render(&args)
})
})
.collect();
let results = futures::future::join_all(tasks).await;
Ok(())
}
Performance Considerations
Caching
// PromptLibrary caches loaded prompts automatically
let library = PromptLibrary::new();
library.add_directory("./prompts")?; // Loads once
// Multiple gets use cached versions
let prompt1 = library.get("greeting")?; // From cache
let prompt2 = library.get("greeting")?; // From cache
Memory Management
use std::sync::Arc;
// Share prompts across threads
let prompt = Arc::new(library.get("greeting")?);
let handles: Vec<_> = (0..4).map(|_| {
let prompt = Arc::clone(&prompt);
let args = args.clone();
std::thread::spawn(move || {
prompt.render(&args)
})
}).collect();
Batch Operations
// Process multiple prompts efficiently
let prompt_names = vec!["greeting", "farewell", "code-review"];
let results: Result<Vec<_>, _> = prompt_names
.iter()
.map(|name| library.get(name)?.render(&args))
.collect();
Testing
Unit Testing Prompts
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_greeting_prompt() {
let prompt = Prompt::new("greeting", "Hello {{name}}!");
let mut args = HashMap::new();
args.insert("name".to_string(), "World".to_string());
let result = prompt.render(&args).unwrap();
assert_eq!(result, "Hello World!");
}
#[test]
fn test_missing_argument() {
let prompt = Prompt::new("greeting", "Hello {{name}}!")
.add_argument(ArgumentSpec {
name: "name".to_string(),
required: true,
..Default::default()
});
let args = HashMap::new(); // Missing 'name'
assert!(prompt.render(&args).is_err());
}
}
Integration Testing
#[cfg(test)]
mod integration_tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_library_from_directory() {
let temp_dir = TempDir::new().unwrap();
let prompt_content = r#"---
title: Test Prompt
---
Hello {{name}}!"#;
std::fs::write(
temp_dir.path().join("test.md"),
prompt_content
).unwrap();
let mut library = PromptLibrary::new();
library.add_directory(temp_dir.path()).unwrap();
let prompt = library.get("test").unwrap();
assert_eq!(prompt.title.unwrap(), "Test Prompt");
}
}
Best Practices
1. Error Handling
- Always use
Result<T>
return types - Provide meaningful error messages
- Chain operations with
?
operator
2. Resource Management
- Cache
PromptLibrary
instances when possible - Use
Arc<>
for sharing across threads - Clean up temporary resources
3. Template Design
- Keep templates focused and reusable
- Use clear argument names
- Provide default values where appropriate
- Document expected arguments
4. Performance
- Load prompts once, use many times
- Consider async patterns for I/O intensive operations
- Use batch operations for multiple prompts
5. Testing
- Unit test individual prompts
- Integration test library loading
- Mock storage backends for testing
See Also
- Library Examples - Practical usage examples
- API Reference - Complete API documentation
- rustdoc Documentation - Generated API docs
- MCP Protocol - Model Context Protocol integration