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.

-

File Location

-

Tool configs are stored in ~/.smarttools/<tool-name>/config.yaml.

+
+

What You'll Learn

+ +
-

Complete Structure

-
# 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:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableContainsSource
{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:

+
    +
  1. Accept a --tone flag (default: "professional")
  2. +
  3. Rewrite the input in that tone
  4. +
  5. Output just the rewritten text
  6. +
+
+ +
+ 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:

- -

To include literal braces, double them:

-
prompt: |
-  Format as JSON: {{\"key\": \"value\"}}
-  Input: {input}
- -

Categories

-

Standard categories for the registry:

- - -

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?

+ + +""", + "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:

+
    +
  1. Identifies the programming language
  2. +
  3. Explains what the code does (using the language info)
  4. +
  5. Suggests improvements
  6. +
+
+ +
+ 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:

+ + """, "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:

- +

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:

+
    +
  1. Takes CSV data as input
  2. +
  3. Uses Python to count rows and extract headers
  4. +
  5. Asks AI to describe what the data likely represents
  6. +
+
+ +
+ 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?

+ + +""", + "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:

+
    +
  1. Detects whether input is a question or a topic
  2. +
  3. Uses a fast model to generate 3 research angles
  4. +
  5. Uses a powerful model to explore the best angle
  6. +
  7. Adds a code step to format with headers and timestamps
  8. +
+
+ +
+ 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?"), ], }, }