diff --git a/src/smarttools/web/docs_content.py b/src/smarttools/web/docs_content.py index 7456bf1..f5c2e00 100644 --- a/src/smarttools/web/docs_content.py +++ b/src/smarttools/web/docs_content.py @@ -609,587 +609,1278 @@ project. It implements:
}, "yaml-config": { - "title": "Understanding YAML Config", - "description": "Learn the structure of SmartTools configuration files", + "title": "The Anatomy of a SmartTool", + "description": "Master the YAML configuration that powers every SmartTool", "content": """ -Every SmartTool is defined by a YAML configuration file. This guide covers the complete -structure and all available options.
+Every SmartTool is just a YAML file with a secret superpower: it turns plain English +instructions into Unix commands. In the next 10 minutes, you'll understand exactly how that magic works.
-Tool configs are stored in ~/.smarttools/<tool-name>/config.yaml.
What You'll Learn
+# Required fields
-name: my-tool # Tool name (lowercase, hyphens)
-version: "1.0.0" # Semver version string
+The Simplest Tool That Actually Works
+Let's start with something real. Here's a complete, working tool in just 8 lines:
-# Recommended fields
-description: "What this tool does"
-category: text-processing # For registry organization
-tags: # Searchable tags
- - text
- - formatting
+name: shout
+version: "1.0.0"
+description: Makes text LOUD
-# Optional metadata
-author: your-name
-license: MIT
-homepage: https://github.com/you/my-tool
+steps:
+ - type: prompt
+ provider: claude
+ prompt: "Convert this to ALL CAPS with enthusiasm: {input}"
+ output_var: result
-# Arguments (custom CLI flags)
+output: "{result}"
+
+Save this to ~/.smarttools/shout/config.yaml and you've got a working command:
+
+$ echo "hello world" | shout
+HELLO WORLD!!!
+
+That's it. Everything else is just adding features to this basic pattern.
+
+The Five Parts of Every Tool
+
+Think of a SmartTool config like a recipe card:
+
+
+
+ 1. Identity
+ name, version, description
+ Who is this tool?
+
+
+ 2. Arguments
+ Custom flags like --format
+ What options does it accept?
+
+
+ 3. Steps
+ Prompts and code blocks
+ What does it do?
+
+
+ 4. Output
+ The final template
+ What comes out?
+
+
+
+Let's see all five in action:
+
+# 1. IDENTITY - Who is this tool?
+name: translate
+version: "1.0.0"
+description: Translate text to any language
+
+# 2. ARGUMENTS - What knobs can users turn?
arguments:
- - flag: --format
- variable: format
- default: "markdown"
- description: Output format
+ - flag: --lang
+ variable: language
+ default: "Spanish"
+ description: Target language
-# Processing steps
+# 3. STEPS - The actual work
steps:
- type: prompt
provider: claude
prompt: |
- Process this: {input}
+ Translate this text to {language}:
+
+ {input}
+ output_var: translation
+
+# 4. OUTPUT - What comes out the other end
+output: "{translation}"
+
+Variables Are Pipes
+
+Here's the mental model that makes everything click: variables are pipes that carry data through your tool.
+
+# The user runs:
+echo "Hello" | translate --lang French
+
+Inside your tool, three pipes are now flowing:
+
+
+
+
+ Variable
+ Contains
+ Source
+
+
+
+
+ {input}
+ "Hello"
+ Piped in from stdin
+
+
+ {language}
+ "French"
+ From --lang flag
+
+
+ {translation}
+ "Bonjour"
+ Created by the AI step
+
+
+
+
+You can use any variable in any step that comes after it's created. They flow downstream, never up.
+
+The YAML Trap Everyone Falls Into
+
+
+ Warning: Unquoted Numbers
+ YAML is helpful in ways that will hurt you. Watch out for versions:
+
+
+# WRONG - YAML sees this as the number 1.0
+version: 1.0
+
+# RIGHT - YAML sees this as the string "1.0.0"
+version: "1.0.0"
+
+Always quote your version numbers. Always. Even if they look fine. Future you will thank present you.
+
+Try It Yourself
+
+
+ Exercise: Build a Tone Shifter
+ Create a tool that rewrites text in different tones. It should:
+
+ - Accept a
--tone flag (default: "professional")
+ - Rewrite the input in that tone
+ - Output just the rewritten text
+
+
+
+
+ Click to see the solution
+ name: tone-shift
+version: "1.0.0"
+description: Rewrite text in a different tone
+
+arguments:
+ - flag: --tone
+ variable: tone
+ default: "professional"
+ description: "Target tone (casual, professional, enthusiastic, formal)"
+
+steps:
+ - type: prompt
+ provider: claude
+ prompt: |
+ Rewrite this text with a {tone} tone.
+ Keep the meaning but change the style.
+
+ Text: {input}
output_var: result
-# Final output template
output: "{result}"
+
-Required Fields
+Naming Your Tools
-name
-The tool's identifier. Must be lowercase with hyphens only:
-name: my-cool-tool # Good
-name: MyCoolTool # Bad - no uppercase
-name: my_cool_tool # Bad - no underscores
+Tool names become Unix commands, so they follow Unix conventions:
-version
-Semantic version string. Always quote it to prevent YAML parsing issues:
-version: "1.0.0" # Good
-version: 1.0 # Bad - YAML parses as float
+# GOOD - lowercase, hyphens
+name: code-review
+name: fix-grammar
+name: json2csv
+
+# BAD - these won't work
+name: CodeReview # No uppercase
+name: fix_grammar # No underscores
+name: fix grammar # No spaces
+
+Pick names that complete the sentence: "I need to _____ this file."
+
+Where To Next?
+
+You now understand the structure. Time to add superpowers:
-Variable Substitution
-Use {variable} syntax in prompts and output:
- {input} - Content piped to the tool
- {variable_name} - From arguments or previous steps
+ - Custom Arguments - Add flags like a pro
+ - Multi-Step Workflows - Chain AI calls together
+ - Code Steps - Mix Python into your tools
-
-To include literal braces, double them:
-prompt: |
- Format as JSON: {{\"key\": \"value\"}}
- Input: {input}
-
-Categories
-Standard categories for the registry:
-
- text-processing - Summarize, translate, format
- code-analysis - Review, explain, generate
- data-extraction - Parse, extract, convert
- content-creation - Write, expand, draft
- productivity - Automate, organize
- education - Explain, teach, simplify
-
-
-Validation
-Test your config without running:
-# Validate syntax
-smarttools test my-tool --dry-run
-
-# Check for common issues
-smarttools registry publish --dry-run
""",
"headings": [
- ("file-location", "File Location"),
- ("complete-structure", "Complete Structure"),
- ("required-fields", "Required Fields"),
- ("variable-substitution", "Variable Substitution"),
- ("categories", "Categories"),
- ("validation", "Validation"),
+ ("the-simplest-tool", "The Simplest Tool That Works"),
+ ("the-five-parts", "The Five Parts of Every Tool"),
+ ("variables-are-pipes", "Variables Are Pipes"),
+ ("the-yaml-trap", "The YAML Trap"),
+ ("try-it", "Try It Yourself"),
+ ("naming-rules", "Naming Your Tools"),
+ ("where-to-next", "Where To Next?"),
],
},
"arguments": {
- "title": "Custom Arguments",
- "description": "Add flags and options to make your tools flexible",
+ "title": "Adding Knobs and Switches",
+ "description": "Give your tools superpowers with custom flags",
"content": """
-Arguments let users customize tool behavior with CLI flags like --format json
-or --verbose.
+The difference between a good tool and a great tool? Options. Let's add
+--lang french and --format json to your toolkit.
-Basic Syntax
-arguments:
- - flag: --format # The CLI flag
- variable: format # Variable name in templates
- default: "text" # Default value if not provided
- description: "Output format (text, json, markdown)"
+
+ What You'll Build
+ A flexible summarizer with adjustable length and style options.
+
-Using Arguments
-Reference arguments in prompts using {variable_name}:
-arguments:
- - flag: --tone
- variable: tone
- default: "professional"
+Your First Flag
+
+Let's add a --style flag to control how our summary sounds:
+
+name: summarize
+version: "1.0.0"
+description: Summarize text with style
+
+arguments:
+ - flag: --style
+ variable: style
+ default: "concise"
+ description: "Style: concise, detailed, or bullet-points"
steps:
- type: prompt
provider: claude
prompt: |
- Rewrite this text with a {tone} tone:
+ Summarize this in a {style} style:
{input}
- output_var: result
-
-Users can then run:
-echo "Hey, fix this bug ASAP!" | tone-shift --tone friendly
-
-Multiple Arguments
-arguments:
- - flag: --lang
- variable: language
- default: "English"
- description: "Target language"
-
- - flag: --formality
- variable: formality
- default: "neutral"
- description: "Formality level (casual, neutral, formal)"
-
- - flag: --max-length
- variable: max_length
- default: "500"
- description: "Maximum output length in words"
-
-Argument Patterns
-
-Choice Arguments
-Document valid choices in the description:
-- flag: --style
- variable: style
- default: "concise"
- description: "Writing style: concise, detailed, or academic"
-
-Numeric Arguments
-Always quote defaults to avoid YAML issues:
-- flag: --max-tokens
- variable: max_tokens
- default: "1000" # Quoted string, not integer
-
-Boolean-like Arguments
-Use string values for conditional prompts:
-- flag: --verbose
- variable: verbose
- default: "no"
- description: "Include detailed explanations (yes/no)"
-
-Using in Prompts
-Combine multiple arguments in your prompt template:
-steps:
- - type: prompt
- provider: claude
- prompt: |
- Translate the following text to {language}.
- Use a {formality} register.
- Keep the response under {max_length} words.
-
- Text to translate:
- {input}
- output_var: translation
-
-Best Practices
-
- - Use descriptive variable names -
target_language not tl
- - Provide sensible defaults - Tools should work without any flags
- - Document choices - List valid options in the description
- - Keep flags short - Use
--lang not --target-language
-
-""",
- "headings": [
- ("basic-syntax", "Basic Syntax"),
- ("using-arguments", "Using Arguments"),
- ("multiple-arguments", "Multiple Arguments"),
- ("argument-types", "Argument Patterns"),
- ("in-prompts", "Using in Prompts"),
- ("best-practices", "Best Practices"),
- ],
- },
-
- "multi-step": {
- "title": "Multi-Step Workflows",
- "description": "Chain prompts and code steps together",
- "content": """
-Complex tools can chain multiple steps together. Each step's output becomes available
-to subsequent steps.
-
-How Steps Flow
-steps:
- # Step 1: Extract key points
- - type: prompt
- provider: claude
- prompt: "Extract 5 key points from: {input}"
- output_var: key_points
-
- # Step 2: Use step 1's output
- - type: prompt
- provider: claude
- prompt: |
- Create a summary from these points:
- {key_points}
output_var: summary
output: "{summary}"
-Variables flow through the pipeline:
-
- {input} → available in all steps
- {key_points} → available after step 1
- {summary} → available after step 2
-
+Now you can run:
-Mixing Prompt and Code Steps
-Combine AI calls with Python processing:
-steps:
- # Step 1: AI extracts data
+# Default (concise)
+cat article.txt | summarize
+
+# Detailed analysis
+cat article.txt | summarize --style detailed
+
+# Quick bullet points
+cat article.txt | summarize --style bullet-points
+
+Notice how the flag value flows right into {style} in the prompt. That's the magic.
+
+Anatomy of an Argument
+
+Every argument has four parts:
+
+arguments:
+ - flag: --style # What users type
+ variable: style # Name in your templates
+ default: "concise" # Value when flag is omitted
+ description: "How to write the summary" # Help text
+
+
+ Pro Tip: The Golden Rule
+ Your tool should work perfectly with zero flags. Defaults are for
+ the 80% case. Flags are for the other 20%.
+
+
+Stacking Multiple Flags
+
+Real tools need multiple options. Here's a translation tool with three knobs:
+
+name: translate
+version: "1.0.0"
+description: Translate with control
+
+arguments:
+ - flag: --to
+ variable: target_lang
+ default: "Spanish"
+ description: "Target language"
+
+ - flag: --tone
+ variable: tone
+ default: "neutral"
+ description: "Tone: casual, neutral, formal"
+
+ - flag: --preserve
+ variable: preserve
+ default: "meaning"
+ description: "Preserve: meaning, structure, or both"
+
+steps:
- type: prompt
provider: claude
prompt: |
- Extract all email addresses from this text as a comma-separated list:
+ Translate to {target_lang}.
+ Use a {tone} tone.
+ Preserve the {preserve}.
+
+ Text:
+ {input}
+ output_var: result
+
+output: "{result}"
+
+Now you have fine-grained control:
+
+# Simple translation
+cat email.txt | translate --to French
+
+# Formal business translation
+cat email.txt | translate --to Japanese --tone formal
+
+# Technical translation preserving structure
+cat docs.md | translate --to German --preserve structure
+
+The Art of Good Flags
+
+
+
+ Do This
+
+ --lang not --target-language
+ --max not --maximum-length
+ --style not --s
+
+
+
+ Not This
+
+ -l (too cryptic)
+ --target_language (underscores feel wrong)
+ --TargetLanguage (this isn't Java)
+
+
+
+
+The Numeric Gotcha
+
+
+ Warning: Quote Your Numbers!
+ YAML defaults are strings. Always quote numbers:
+
+
+# WRONG - YAML might do weird things
+default: 100
+
+# RIGHT - Always a string
+default: "100"
+
+In your prompt, it'll work the same either way—but quoting prevents surprises.
+
+Try It: Build a Code Reviewer
+
+
+ Exercise
+ Create a code review tool with these flags:
+
+ --focus - What to focus on (bugs, style, performance)
+ --severity - Minimum severity to report (low, medium, high)
+
+
+
+
+ See the solution
+ name: review
+version: "1.0.0"
+description: AI code review with focus
+
+arguments:
+ - flag: --focus
+ variable: focus
+ default: "bugs"
+ description: "Focus: bugs, style, performance, security"
+
+ - flag: --severity
+ variable: severity
+ default: "medium"
+ description: "Minimum severity: low, medium, high"
+
+steps:
+ - type: prompt
+ provider: claude
+ prompt: |
+ Review this code. Focus on {focus} issues.
+ Only report issues of {severity} severity or higher.
+
+ Code:
+ {input}
+ output_var: review
+
+output: "{review}"
+
+
+Next Up
+
+You've mastered single-step tools with arguments. Ready for the real power?
+
+
+ - Multi-Step Workflows - Chain multiple AI calls
+ - Code Steps - Add Python processing
+
+""",
+ "headings": [
+ ("your-first-flag", "Your First Flag"),
+ ("anatomy-of-an-argument", "Anatomy of an Argument"),
+ ("stacking-flags", "Stacking Multiple Flags"),
+ ("flag-design", "The Art of Good Flags"),
+ ("numeric-gotcha", "The Numeric Gotcha"),
+ ("try-it", "Try It: Build a Code Reviewer"),
+ ("next-up", "Next Up"),
+ ],
+ },
+
+ "multi-step": {
+ "title": "Chaining AI Like a Pro",
+ "description": "Build powerful pipelines by connecting multiple AI calls",
+ "content": """
+One AI call is nice. But the real magic happens when you chain them together—feeding
+the output of one model into the input of the next. Think of it as building an assembly line for intelligence.
+
+
+ What You'll Learn
+
+ - How to pass data between multiple AI steps
+ - Mixing AI calls with Python processing
+ - The three patterns that solve 90% of multi-step problems
+
+
+
+Your First Pipeline
+
+Let's build something real: a tool that extracts key points from an article, then turns them into a tweet thread.
+
+name: article-to-tweets
+version: "1.0.0"
+description: Turn articles into tweet threads
+
+steps:
+ # Step 1: Extract the essence
+ - type: prompt
+ provider: claude
+ prompt: |
+ Extract 5 key insights from this article.
+ Be specific and quotable.
+
+ {input}
+ output_var: key_points
+
+ # Step 2: Transform into tweets
+ - type: prompt
+ provider: claude
+ prompt: |
+ Turn these insights into a compelling tweet thread.
+ Each tweet under 280 characters.
+ Make the first tweet a hook.
+
+ Insights:
+ {key_points}
+ output_var: thread
+
+output: "{thread}"
+
+The magic is in line 20: {key_points} contains the output from Step 1. Data flows downstream automatically.
+
+The Variable Waterfall
+
+Think of variables like water flowing downhill—they only go forward, never back:
+
+
+
+
+
+ 📥
+
+ Input
+ {input}
+
+ →
+
+
+ 🤖
+
+ Step 1
+ {key_points}
+
+ →
+
+
+ 🤖
+
+ Step 2
+ {thread}
+
+ →
+
+
+ 📤
+
+ Output
+
+
+
+
+Step 2 can use: {input}, {key_points}
+Step 2 cannot use: Variables from Step 3 (doesn't exist yet!)
+
+Adding Python Glue
+
+AI is great at language. Python is great at data wrangling. Together? Unstoppable.
+
+name: email-extractor
+version: "1.0.0"
+description: Extract and deduplicate emails from messy text
+
+steps:
+ # AI finds the emails
+ - type: prompt
+ provider: claude
+ prompt: |
+ Extract all email addresses from this text.
+ Return them comma-separated, nothing else.
+
{input}
output_var: emails_raw
- # Step 2: Python cleans the data
+ # Python cleans them up
- type: code
code: |
- emails = [e.strip() for e in emails_raw.split(',')]
- emails = [e for e in emails if '@' in e]
- email_count = len(emails)
- cleaned_emails = '\\n'.join(sorted(set(emails)))
- output_var: cleaned_emails, email_count
+ # Split, clean, deduplicate
+ emails = [e.strip().lower() for e in emails_raw.split(',')]
+ emails = [e for e in emails if '@' in e and '.' in e]
+ unique_emails = sorted(set(emails))
+ count = len(unique_emails)
+ clean_list = '\\n'.join(unique_emails)
+ output_var: clean_list, count
- # Step 3: AI formats output
+ # AI formats the output nicely
- type: prompt
provider: claude
prompt: |
- Format these {email_count} emails as a nice list:
- {cleaned_emails}
- output_var: formatted
+ Format these {count} email addresses as a clean list
+ with any obvious categorization (personal, work, etc):
-output: "{formatted}"
+ {clean_list}
+ output_var: result
+
+output: "{result}"
+
+
+ Pro Tip: Code Steps Return Multiple Variables
+ Notice output_var: clean_list, count—you can export multiple variables
+ from a single code step. Just list them comma-separated.
+
+
+The Three Patterns That Solve Everything
+
+
+
+ Extract → Transform → Format
+ prompt # Pull out data
+code # Clean/filter
+prompt # Make it pretty
+ Use for: Data extraction, report generation
+
+
+
+ Analyze → Synthesize
+ prompt # Break it down
+prompt # Build it up
+ Use for: Summaries, insights, rewriting
+
+
+
+ Validate → Process
+ code # Check input
+prompt # Do the work
+ Use for: Safe processing, error handling
+
+
+
+When Things Go Wrong
+
+
+ Warning: Steps Are All-Or-Nothing
+ If Step 2 fails, Step 3 never runs. Design defensively!
+
-Step Dependencies
-If any step fails, execution stops. Design steps to handle edge cases:
steps:
+ # Guard clause in code
- type: code
code: |
- # Handle empty input gracefully
if not input.strip():
- result = "No input provided"
- skip_ai = "yes"
+ processed = "ERROR: Empty input"
+ is_valid = False
else:
- result = input
- skip_ai = "no"
- output_var: result, skip_ai
+ processed = input
+ is_valid = True
+ output_var: processed, is_valid
+
+ # Only meaningful if valid
+ - type: prompt
+ provider: claude
+ prompt: |
+ Summarize this text: {processed}
+ output_var: summary
+
+Try It: Build a Code Explainer
+
+
+ Exercise
+ Build a tool that:
+
+ - Identifies the programming language
+ - Explains what the code does (using the language info)
+ - Suggests improvements
+
+
+
+
+ See the solution
+ name: code-explainer
+version: "1.0.0"
+description: Understand and improve any code
+
+steps:
+ - type: prompt
+ provider: claude
+ prompt: |
+ What programming language is this?
+ Reply with just the language name.
+
+ {input}
+ output_var: language
- type: prompt
provider: claude
prompt: |
- {result}
- # AI prompt only runs if skip_ai is "no"
- output_var: ai_response
+ Explain this {language} code in plain English.
+ Describe what each part does.
-Common Patterns
+ {input}
+ output_var: explanation
-Extract → Transform → Format
-steps:
- - type: prompt # Extract structured data
- - type: code # Transform/filter
- - type: prompt # Format for output
+ - type: prompt
+ provider: claude
+ prompt: |
+ Suggest 3 improvements for this {language} code.
+ Consider readability, performance, and best practices.
-Analyze → Synthesize
-steps:
- - type: prompt # Break down into parts
- - type: prompt # Combine insights
+ Code:
+ {input}
-Validate → Process
-steps:
- - type: code # Validate input format
- - type: prompt # Process if valid
+ Current understanding:
+ {explanation}
+ output_var: improvements
-Debugging Multi-Step Tools
-# Show prompts without running
+output: |
+ ## Language: {language}
+
+ ## Explanation
+ {explanation}
+
+ ## Suggested Improvements
+ {improvements}
+
+
+Debugging Multi-Step Tools
+
+# See what prompts are being built
cat test.txt | my-tool --dry-run
-# See verbose output
-cat test.txt | my-tool --verbose
+# Watch each step execute
+cat test.txt | my-tool --verbose
+
+# Test with mock provider first
+cat test.txt | my-tool --provider mock
+
+Next Up
+
+Ready to go deeper? Learn the full power of code steps:
+
+
+ - Code Steps Deep Dive - Python superpowers in your tools
+ - Advanced Workflows - Multi-provider, conditional logic, and more
+
""",
"headings": [
- ("step-flow", "How Steps Flow"),
- ("mixed-steps", "Mixing Prompt and Code Steps"),
- ("error-handling", "Step Dependencies"),
- ("common-patterns", "Common Patterns"),
- ("debugging", "Debugging Multi-Step Tools"),
+ ("your-first-pipeline", "Your First Pipeline"),
+ ("the-variable-waterfall", "The Variable Waterfall"),
+ ("adding-python-glue", "Adding Python Glue"),
+ ("the-three-patterns", "The Three Patterns"),
+ ("when-things-go-wrong", "When Things Go Wrong"),
+ ("try-it", "Try It: Build a Code Explainer"),
+ ("debugging-tips", "Debugging Tips"),
+ ("next-up", "Next Up"),
],
},
"code-steps": {
- "title": "Code Steps",
- "description": "Add Python code processing between AI calls",
+ "title": "Python Superpowers",
+ "description": "Inject Python into your AI workflows for ultimate control",
"content": """
-Code steps let you run Python code to process data, validate input, or transform
-AI outputs between prompts.
+AI is great at understanding language. But sometimes you need Python's precision: parsing JSON,
+filtering data, doing math, or talking to APIs. Code steps let you mix Python into your AI workflows
+like a secret ingredient.
+
+
+ What You'll Learn
+
+ - How to embed Python code in your tools
+ - Accessing variables from previous steps
+ - The cookbook of common operations
+ - Error handling that doesn't break your tools
+
+
+
+Your First Code Step
+
+The simplest code step looks like this:
-Basic Syntax
steps:
- type: code
code: |
- # Python code here
result = input.upper()
output_var: result
-Available Variables
-Code steps have access to:
-
- input - The original input text
- - All argument variables
- - Output variables from previous steps
-
+That's it. Whatever you assign to result becomes available as {result} in subsequent steps.
-arguments:
- - flag: --max
- variable: max_items
- default: "10"
+
+ What you have access to:
+
+
+ input
+ The original stdin
+
+
+ arg_name
+ Any --flag values
+
+
+ prev_output
+ Previous step outputs
+
+
+
+
+A Real Example: Smart Word Counter
+
+Let's build something useful—a word counter that also finds the most common words:
+
+name: wordstats
+version: "1.0.0"
+description: Analyze text statistics
steps:
- - type: prompt
- prompt: "List items from: {input}"
- output_var: items_raw
-
- type: code
code: |
- # Access argument and previous step output
- items = items_raw.strip().split('\\n')
- limited = items[:int(max_items)]
- result = '\\n'.join(limited)
- output_var: result
+ import re
+ from collections import Counter
-Multiple Output Variables
-Return multiple values with comma-separated output_var:
-- type: code
- code: |
- lines = input.strip().split('\\n')
- line_count = len(lines)
- word_count = len(input.split())
- char_count = len(input)
- output_var: line_count, word_count, char_count
+ # Clean and split
+ words = re.findall(r'\\b\\w+\\b', input.lower())
-Common Operations
+ # Calculate stats
+ word_count = len(words)
+ unique_count = len(set(words))
+ char_count = len(input)
-Text Processing
-- type: code
- code: |
- # Remove empty lines
- lines = [l for l in input.split('\\n') if l.strip()]
- cleaned = '\\n'.join(lines)
- output_var: cleaned
+ # Find top words
+ top_words = Counter(words).most_common(5)
+ top_formatted = '\\n'.join(f" {w}: {c}" for w, c in top_words)
+ output_var: word_count, unique_count, char_count, top_formatted
-JSON Parsing
-- type: code
- code: |
- import json
- data = json.loads(ai_response)
- formatted = json.dumps(data, indent=2)
- output_var: formatted
+output: |
+ Words: {word_count}
+ Unique: {unique_count}
+ Characters: {char_count}
-Data Validation
-- type: code
- code: |
- import re
- emails = re.findall(r'[\\w.-]+@[\\w.-]+', input)
- valid_emails = '\\n'.join(emails) if emails else "No emails found"
- output_var: valid_emails
+ Top 5 words:
+ {top_formatted}
-File Operations
-- type: code
- code: |
- from pathlib import Path
- # Write to temp file
- output_path = Path('/tmp/output.txt')
- output_path.write_text(processed_text)
- result = f"Saved to {output_path}"
- output_var: result
+
+ Pro Tip: Export Multiple Variables
+ Notice output_var: word_count, unique_count, char_count, top_formatted—
+ you can export as many variables as you need. Just list them comma-separated.
+
-Using Imports
-Standard library imports work in code steps:
-- type: code
- code: |
- import json
- import re
- from datetime import datetime
- from pathlib import Path
+The Code Step Cookbook
- timestamp = datetime.now().isoformat()
- result = f"Processed at {timestamp}"
- output_var: result
+Copy-paste these recipes for common tasks:
-Error Handling
-Handle exceptions to prevent tool failures:
-- type: code
+
+
+ Parse JSON from AI
+ - type: code
code: |
import json
try:
data = json.loads(ai_response)
- result = data.get('summary', 'No summary found')
+ result = json.dumps(data, indent=2)
except json.JSONDecodeError:
- result = ai_response # Fall back to raw response
+ result = ai_response # Fallback to raw
output_var: result
+
-Security Notes
-
- - Code runs with your user permissions
- - Don't use
eval() on untrusted input
- - Be careful with file operations
- - Third-party packages must be installed separately
-
-""",
- "headings": [
- ("basic-syntax", "Basic Syntax"),
- ("available-variables", "Available Variables"),
- ("multiple-outputs", "Multiple Output Variables"),
- ("common-operations", "Common Operations"),
- ("using-imports", "Using Imports"),
- ("error-handling", "Error Handling"),
- ("security", "Security Notes"),
- ],
- },
+
+ Extract with Regex
+ - type: code
+ code: |
+ import re
+ # Find all emails
+ emails = re.findall(r'[\\w.-]+@[\\w.-]+\\.\\w+', input)
+ result = '\\n'.join(emails) or "No emails found"
+ output_var: result
+
- "advanced-workflows": {
- "title": "Advanced Workflows",
- "description": "Complex multi-provider and advanced tool patterns",
- "content": """
-Take your tools to the next level with advanced patterns like multi-provider
-workflows, dynamic prompts, and complex data pipelines.
+
+ Remove Empty Lines
+ - type: code
+ code: |
+ lines = [l for l in input.split('\\n') if l.strip()]
+ cleaned = '\\n'.join(lines)
+ output_var: cleaned
+
-Multi-Provider Workflows
-Use different AI providers for different tasks:
-steps:
- # Fast model for extraction
- - type: prompt
- provider: opencode-grok
- prompt: "Extract key facts from: {input}"
- output_var: facts
+
+ Add Timestamps
+ - type: code
+ code: |
+ from datetime import datetime
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
+ result = f"[{timestamp}] {input}"
+ output_var: result
+
- # Powerful model for synthesis
- - type: prompt
- provider: claude-opus
- prompt: |
- Create a comprehensive analysis from these facts:
- {facts}
- output_var: analysis
+
+ Limit/Truncate Text
+ - type: code
+ code: |
+ max_len = int(max_length) # From --max-length flag
+ if len(input) > max_len:
+ truncated = input[:max_len] + "..."
+ else:
+ truncated = input
+ output_var: truncated
+
+
-Conditional Logic with Code
-Use code steps to implement branching:
-steps:
- # Analyze input type
- - type: code
- code: |
- if input.strip().startswith('{'):
- input_type = "json"
- processed = input
- elif ',' in input and '\\n' in input:
- input_type = "csv"
- processed = input
- else:
- input_type = "text"
- processed = input
- output_var: input_type, processed
+Bulletproof Error Handling
- # Different prompt based on type
+
+ Warning: Crashes Kill Your Tool
+ An uncaught exception stops execution immediately. Always wrap risky code in try/except.
+
+
+- type: code
+ code: |
+ import json
+
+ try:
+ # Risky operation
+ data = json.loads(ai_response)
+ items = data.get('items', [])
+ result = '\\n'.join(items)
+ error = None
+ except json.JSONDecodeError as e:
+ # Graceful fallback
+ result = ai_response
+ error = f"Parse warning: {e}"
+ except KeyError as e:
+ result = "Missing expected field"
+ error = str(e)
+ output_var: result, error
+
+The AI + Code Power Combo
+
+The real magic happens when you alternate between AI and code:
+
+name: structured-extract
+version: "1.0.0"
+description: Extract and validate structured data
+
+steps:
+ # AI extracts data
- type: prompt
provider: claude
prompt: |
- This is {input_type} data. Analyze it appropriately:
- {processed}
- output_var: result
+ Extract all dates from this text.
+ Return as JSON array: ["YYYY-MM-DD", ...]
-Iterative Refinement
-Multiple passes for quality improvement:
-steps:
- # First draft
- - type: prompt
- provider: opencode-deepseek
- prompt: "Write a summary of: {input}"
- output_var: draft
+ {input}
+ output_var: dates_json
- # Critique
+ # Code validates and sorts
+ - type: code
+ code: |
+ import json
+ from datetime import datetime
+
+ try:
+ dates = json.loads(dates_json)
+ # Validate each date
+ valid = []
+ for d in dates:
+ try:
+ datetime.strptime(d, "%Y-%m-%d")
+ valid.append(d)
+ except ValueError:
+ pass
+ # Sort chronologically
+ sorted_dates = sorted(valid)
+ result = '\\n'.join(sorted_dates)
+ count = len(sorted_dates)
+ except json.JSONDecodeError:
+ result = "Could not parse dates"
+ count = 0
+ output_var: result, count
+
+ # AI formats nicely
- type: prompt
- provider: claude-haiku
+ provider: claude
prompt: |
- Review this summary for accuracy and clarity.
- List specific improvements needed:
- {draft}
- output_var: critique
+ Format these {count} dates in a human-readable way:
+ {result}
+ output_var: formatted
- # Final version
- - type: prompt
- provider: claude-sonnet
- prompt: |
- Improve this summary based on the feedback:
+output: "{formatted}"
- Original: {draft}
+Try It: Build a CSV Analyzer
- Feedback: {critique}
- output_var: final
+
+ Exercise
+ Create a tool that:
+
+ - Takes CSV data as input
+ - Uses Python to count rows and extract headers
+ - Asks AI to describe what the data likely represents
+
+
+
+
+ See the solution
+ name: csv-describe
+version: "1.0.0"
+description: Understand CSV data at a glance
-Data Processing Pipelines
-name: csv-analyzer
steps:
- # Parse CSV
- type: code
code: |
import csv
from io import StringIO
- reader = csv.DictReader(StringIO(input))
- rows = list(reader)
- headers = list(rows[0].keys()) if rows else []
- row_count = len(rows)
- sample = rows[:5]
- output_var: headers, row_count, sample
- # AI analysis
+ reader = csv.reader(StringIO(input))
+ rows = list(reader)
+
+ if rows:
+ headers = rows[0]
+ row_count = len(rows) - 1
+ sample = rows[1:4] # First 3 data rows
+ headers_str = ', '.join(headers)
+ sample_str = '\\n'.join(', '.join(r) for r in sample)
+ else:
+ headers_str = "No headers"
+ row_count = 0
+ sample_str = "No data"
+ output_var: headers_str, row_count, sample_str
+
- type: prompt
provider: claude
prompt: |
- Analyze this CSV data:
- - Columns: {headers}
- - Row count: {row_count}
- - Sample rows: {sample}
+ This CSV has {row_count} rows with these columns:
+ {headers_str}
- Provide insights about the data structure and patterns.
+ Sample data:
+ {sample_str}
+
+ What does this data likely represent?
+ What insights could we extract from it?
output_var: analysis
- # Generate code
+output: |
+ Columns: {headers_str}
+ Rows: {row_count}
+
+ {analysis}
+
+
+What You Can Import
+
+The Python standard library is fully available:
+
+
+ json
+ re
+ csv
+ datetime
+ pathlib
+ collections
+ itertools
+ math
+
+
+Third-party packages work too—just make sure they're installed in your environment.
+
+Security: The Rules
+
+
+ Never Do This
+
+ eval(input) - Code injection waiting to happen
+ exec(user_data) - Same problem
+ os.system(input) - Shell injection
+
+
+
+Code steps run with your user permissions. Treat input as untrusted data—parse it, don't execute it.
+
+Level Up
+
+Ready for the advanced stuff?
+
+
+ - Advanced Workflows - Multi-provider, conditional logic, external tools
+ - Parallel Orchestration - Run multiple tools simultaneously
+
+""",
+ "headings": [
+ ("hello-code", "Your First Code Step"),
+ ("real-example", "A Real Example"),
+ ("the-cookbook", "The Code Step Cookbook"),
+ ("error-handling", "Bulletproof Error Handling"),
+ ("combining-with-ai", "The AI + Code Power Combo"),
+ ("try-it", "Try It: Build a CSV Analyzer"),
+ ("what-you-can-import", "What You Can Import"),
+ ("security", "Security: The Rules"),
+ ("next-up", "Level Up"),
+ ],
+ },
+
+ "advanced-workflows": {
+ "title": "The Advanced Playbook",
+ "description": "Patterns that separate the pros from the beginners",
+ "content": """
+You've mastered the basics. Now it's time for the patterns that make people ask,
+"Wait, you built that with YAML?" Multi-provider orchestration, conditional logic, self-improving
+loops, and more.
+
+
+ What You'll Master
+
+ - Using different AI models for different tasks
+ - Building tools that adapt to their input
+ - Self-critiquing workflows that iterate to perfection
+ - Calling external tools from within your SmartTools
+
+
+
+Right Model, Right Job
+
+Different AI models have different strengths. A smart tool uses them strategically:
+
+
+
+ Fast & Cheap
+ Haiku, Grok-mini
+ Extraction, formatting, simple tasks
+
+
+ Balanced
+ Sonnet, GPT-4o-mini
+ Analysis, writing, code review
+
+
+ Maximum Power
+ Opus, GPT-4, DeepSeek
+ Complex reasoning, synthesis
+
+
+
+name: smart-analyzer
+version: "1.0.0"
+description: Uses the right model for each task
+
+steps:
+ # Fast model extracts structure
+ - type: prompt
+ provider: opencode-grok
+ prompt: |
+ Extract all key facts, dates, and names from this text.
+ Return as a bullet list, nothing else.
+
+ {input}
+ output_var: facts
+
+ # Powerful model does the thinking
+ - type: prompt
+ provider: claude-sonnet
+ prompt: |
+ Based on these extracted facts, provide:
+ 1. A summary of what happened
+ 2. Key insights or patterns
+ 3. Questions that remain unanswered
+
+ Facts:
+ {facts}
+ output_var: analysis
+
+output: "{analysis}"
+
+
+ Pro Tip: Cost Optimization
+ Use fast models for extraction (structured data from messy text) and powerful
+ models for synthesis (insights from structured data). You'll cut costs by 80% with no quality loss.
+
+
+Tools That Adapt to Their Input
+
+The best tools don't assume what they're getting. They figure it out:
+
+name: universal-parser
+version: "1.0.0"
+description: Handles JSON, CSV, or plain text
+
+steps:
+ # Detect format
+ - type: code
+ code: |
+ import json
+
+ text = input.strip()
+
+ if text.startswith('{') or text.startswith('['):
+ try:
+ json.loads(text)
+ format_type = "json"
+ except:
+ format_type = "text"
+ elif ',' in text and '\\n' in text:
+ format_type = "csv"
+ else:
+ format_type = "text"
+
+ format_instructions = {
+ "json": "Parse this JSON and describe its structure.",
+ "csv": "Analyze this CSV data and summarize the columns.",
+ "text": "Summarize the key points of this text."
+ }
+ instruction = format_instructions[format_type]
+ output_var: format_type, instruction
+
+ # Process accordingly
- type: prompt
provider: claude
prompt: |
- Based on this analysis: {analysis}
+ This input is {format_type} format.
- Write Python code to process this CSV and extract key metrics.
- output_var: code
+ {instruction}
-Template Composition
-Build prompts dynamically:
-arguments:
+ Input:
+ {input}
+ output_var: result
+
+output: "[{format_type}] {result}"
+
+The Self-Improving Loop
+
+Want better quality? Make your tool critique itself:
+
+name: perfect-summary
+version: "1.0.0"
+description: Summarizes, then improves itself
+
+steps:
+ # First attempt
+ - type: prompt
+ provider: claude-haiku
+ prompt: |
+ Write a 3-sentence summary of this text:
+
+ {input}
+ output_var: draft
+
+ # Self-critique
+ - type: prompt
+ provider: claude-sonnet
+ prompt: |
+ Rate this summary from 1-10 and list specific improvements:
+
+ Original text:
+ {input}
+
+ Summary:
+ {draft}
+
+ Be harsh but constructive.
+ output_var: critique
+
+ # Final polish
+ - type: prompt
+ provider: claude-sonnet
+ prompt: |
+ Rewrite this summary addressing the feedback.
+ Keep it to 3 sentences.
+
+ Original summary: {draft}
+
+ Feedback: {critique}
+ output_var: final
+
+output: "{final}"
+
+
+ Warning: Know When to Stop
+ More iterations don't always mean better output. 2-3 passes is usually the sweet spot.
+ Beyond that, you're paying for diminishing returns.
+
+
+Dynamic Prompt Building
+
+Let Python construct your prompts on the fly:
+
+name: multi-task
+version: "1.0.0"
+description: One tool, many abilities
+
+arguments:
- flag: --task
variable: task
default: "summarize"
+ description: "Task: summarize, explain, critique, expand, translate"
+
+ - flag: --style
+ variable: style
+ default: "professional"
steps:
- type: code
code: |
- templates = {
- "summarize": "Summarize this concisely:",
- "explain": "Explain this for a beginner:",
- "critique": "Provide constructive criticism of:",
- "expand": "Expand on this with more detail:"
+ prompts = {
+ "summarize": f"Summarize in a {style} tone",
+ "explain": f"Explain for a beginner, {style} style",
+ "critique": f"Provide {style} constructive criticism",
+ "expand": f"Expand with more detail, keep {style}",
+ "translate": f"Translate, maintaining {style} register"
}
- instruction = templates.get(task, templates["summarize"])
+
+ instruction = prompts.get(task, prompts["summarize"])
+
+ # Add context based on input length
+ length = len(input)
+ if length > 5000:
+ instruction += ". Focus on the most important parts."
+ elif length < 100:
+ instruction += ". Be thorough despite the short input."
output_var: instruction
- type: prompt
@@ -1198,50 +1889,180 @@ steps:
{instruction}
{input}
- output_var: result
+ output_var: result
-Integrating External Tools
-steps:
- # Use code to call external commands
+output: "{result}"
+
+Calling External Tools
+
+SmartTools can wrap any command-line tool:
+
+name: lint-explain
+version: "1.0.0"
+description: Runs pylint and explains the results
+
+steps:
+ # Run the linter
- type: code
code: |
import subprocess
- # Run linter
- result = subprocess.run(
- ['pylint', '--output-format=json', '-'],
- input=input,
- capture_output=True,
- text=True
- )
- lint_output = result.stdout
+ import tempfile
+ import os
+
+ # Write code to temp file
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
+ f.write(input)
+ temp_path = f.name
+
+ try:
+ result = subprocess.run(
+ ['pylint', '--output-format=text', temp_path],
+ capture_output=True,
+ text=True,
+ timeout=30
+ )
+ lint_output = result.stdout or result.stderr or "No issues found!"
+ except FileNotFoundError:
+ lint_output = "ERROR: pylint not installed"
+ except subprocess.TimeoutExpired:
+ lint_output = "ERROR: Linting timed out"
+ finally:
+ os.unlink(temp_path)
output_var: lint_output
- # AI interprets results
+ # Explain in plain English
- type: prompt
provider: claude
prompt: |
- Explain these linting results in plain English
- and suggest fixes:
+ Explain these linting results to a Python beginner.
+ For each issue, explain WHY it's a problem and HOW to fix it.
+ Results:
{lint_output}
- output_var: explanation
+ output_var: explanation
+
+output: |
+ ## Lint Results
+ ```
+ {lint_output}
+ ```
+
+ ## Explanation
+ {explanation}
+
+Try It: Build a Research Assistant
+
+
+ Boss Level Exercise
+ Create a tool that:
+
+ - Detects whether input is a question or a topic
+ - Uses a fast model to generate 3 research angles
+ - Uses a powerful model to explore the best angle
+ - Adds a code step to format with headers and timestamps
+
+
+
+
+ See the solution
+ name: research
+version: "1.0.0"
+description: Deep research on any topic
+
+steps:
+ # Detect input type
+ - type: code
+ code: |
+ text = input.strip()
+ is_question = text.endswith('?') or text.lower().startswith(('what', 'how', 'why', 'when', 'who', 'where'))
+ input_type = "question" if is_question else "topic"
+ output_var: input_type
+
+ # Generate angles
+ - type: prompt
+ provider: opencode-grok
+ prompt: |
+ This is a {input_type}: {input}
+
+ Suggest 3 interesting angles to explore this.
+ Return as a numbered list.
+ output_var: angles
+
+ # Deep dive
+ - type: prompt
+ provider: claude-sonnet
+ prompt: |
+ Research request: {input}
+
+ Possible angles:
+ {angles}
+
+ Pick the most interesting angle and provide:
+ 1. Background context
+ 2. Key facts and insights
+ 3. Different perspectives
+ 4. Remaining questions
+ output_var: research
+
+ # Format nicely
+ - type: code
+ code: |
+ from datetime import datetime
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
+ formatted = f'''
+# Research Report
+Generated: {timestamp}
+Query: {input}
+
+{research}
+
+---
+*Angles considered: {angles}*
+'''
+ output_var: formatted
+
+output: "{formatted}"
+
+
+Performance Secrets
+
+
+
+ Speed Tricks
+
+ - Use Haiku/Grok for extraction
+ - Combine related tasks in one prompt
+ - Skip AI when Python can do it
+
+
+
+ Cost Tricks
+
+ - Powerful models only for synthesis
+ - Truncate long inputs with code first
+ - Cache repeated operations
+
+
+
+
+What's Next?
+
+You've learned the advanced patterns. Now go parallel:
-Performance Tips
- - Use fast models for simple tasks - grok for extraction, haiku for formatting
- - Minimize API calls - Combine related tasks in one prompt
- - Cache with code steps - Store intermediate results
- - Parallel execution - See Parallel Orchestration
+ - Parallel Orchestration - Run multiple tools simultaneously
+ - Publishing Tools - Share your creations with the world
""",
"headings": [
- ("multi-provider", "Multi-Provider Workflows"),
- ("conditional-logic", "Conditional Logic with Code"),
- ("iterative-refinement", "Iterative Refinement"),
- ("data-pipelines", "Data Processing Pipelines"),
- ("template-composition", "Template Composition"),
- ("external-tools", "Integrating External Tools"),
- ("performance-tips", "Performance Tips"),
+ ("right-model-right-job", "Right Model, Right Job"),
+ ("adaptive-tools", "Tools That Adapt"),
+ ("self-improving", "The Self-Improving Loop"),
+ ("dynamic-prompts", "Dynamic Prompt Building"),
+ ("external-tools", "Calling External Tools"),
+ ("try-it", "Try It: Research Assistant"),
+ ("performance", "Performance Secrets"),
+ ("whats-next", "What's Next?"),
],
},
}