Add comprehensive tutorial content

- yaml-config: Complete YAML structure reference
- arguments: Custom CLI flags and patterns
- multi-step: Chaining prompts and code steps
- code-steps: Python processing between AI calls
- advanced-workflows: Multi-provider, conditionals, pipelines

Update tutorials route to use docs content system
Expand table of contents with new sections

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-01-01 05:20:33 -04:00
parent c1eb425457
commit d072d7ccad
2 changed files with 654 additions and 2 deletions

View File

@ -607,6 +607,643 @@ project. It implements:</p>
("example-project", "Full Example Project"), ("example-project", "Full Example Project"),
], ],
}, },
"yaml-config": {
"title": "Understanding YAML Config",
"description": "Learn the structure of SmartTools configuration files",
"content": """
<p class="lead">Every SmartTool is defined by a YAML configuration file. This guide covers the complete
structure and all available options.</p>
<h2 id="file-location">File Location</h2>
<p>Tool configs are stored in <code>~/.smarttools/&lt;tool-name&gt;/config.yaml</code>.</p>
<h2 id="complete-structure">Complete Structure</h2>
<pre><code class="language-yaml"># Required fields
name: my-tool # Tool name (lowercase, hyphens)
version: "1.0.0" # Semver version string
# Recommended fields
description: "What this tool does"
category: text-processing # For registry organization
tags: # Searchable tags
- text
- formatting
# Optional metadata
author: your-name
license: MIT
homepage: https://github.com/you/my-tool
# Arguments (custom CLI flags)
arguments:
- flag: --format
variable: format
default: "markdown"
description: Output format
# Processing steps
steps:
- type: prompt
provider: claude
prompt: |
Process this: {input}
output_var: result
# Final output template
output: "{result}"</code></pre>
<h2 id="required-fields">Required Fields</h2>
<h3>name</h3>
<p>The tool's identifier. Must be lowercase with hyphens only:</p>
<pre><code class="language-yaml">name: my-cool-tool # Good
name: MyCoolTool # Bad - no uppercase
name: my_cool_tool # Bad - no underscores</code></pre>
<h3>version</h3>
<p>Semantic version string. Always quote it to prevent YAML parsing issues:</p>
<pre><code class="language-yaml">version: "1.0.0" # Good
version: 1.0 # Bad - YAML parses as float</code></pre>
<h2 id="variable-substitution">Variable Substitution</h2>
<p>Use <code>{variable}</code> syntax in prompts and output:</p>
<ul>
<li><code>{input}</code> - Content piped to the tool</li>
<li><code>{variable_name}</code> - From arguments or previous steps</li>
</ul>
<p>To include literal braces, double them:</p>
<pre><code class="language-yaml">prompt: |
Format as JSON: {{\"key\": \"value\"}}
Input: {input}</code></pre>
<h2 id="categories">Categories</h2>
<p>Standard categories for the registry:</p>
<ul>
<li><code>text-processing</code> - Summarize, translate, format</li>
<li><code>code-analysis</code> - Review, explain, generate</li>
<li><code>data-extraction</code> - Parse, extract, convert</li>
<li><code>content-creation</code> - Write, expand, draft</li>
<li><code>productivity</code> - Automate, organize</li>
<li><code>education</code> - Explain, teach, simplify</li>
</ul>
<h2 id="validation">Validation</h2>
<p>Test your config without running:</p>
<pre><code class="language-bash"># Validate syntax
smarttools test my-tool --dry-run
# Check for common issues
smarttools registry publish --dry-run</code></pre>
""",
"headings": [
("file-location", "File Location"),
("complete-structure", "Complete Structure"),
("required-fields", "Required Fields"),
("variable-substitution", "Variable Substitution"),
("categories", "Categories"),
("validation", "Validation"),
],
},
"arguments": {
"title": "Custom Arguments",
"description": "Add flags and options to make your tools flexible",
"content": """
<p class="lead">Arguments let users customize tool behavior with CLI flags like <code>--format json</code>
or <code>--verbose</code>.</p>
<h2 id="basic-syntax">Basic Syntax</h2>
<pre><code class="language-yaml">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)"</code></pre>
<h2 id="using-arguments">Using Arguments</h2>
<p>Reference arguments in prompts using <code>{variable_name}</code>:</p>
<pre><code class="language-yaml">arguments:
- flag: --tone
variable: tone
default: "professional"
steps:
- type: prompt
provider: claude
prompt: |
Rewrite this text with a {tone} tone:
{input}
output_var: result</code></pre>
<p>Users can then run:</p>
<pre><code class="language-bash">echo "Hey, fix this bug ASAP!" | tone-shift --tone friendly</code></pre>
<h2 id="multiple-arguments">Multiple Arguments</h2>
<pre><code class="language-yaml">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"</code></pre>
<h2 id="argument-types">Argument Patterns</h2>
<h3>Choice Arguments</h3>
<p>Document valid choices in the description:</p>
<pre><code class="language-yaml">- flag: --style
variable: style
default: "concise"
description: "Writing style: concise, detailed, or academic"</code></pre>
<h3>Numeric Arguments</h3>
<p>Always quote defaults to avoid YAML issues:</p>
<pre><code class="language-yaml">- flag: --max-tokens
variable: max_tokens
default: "1000" # Quoted string, not integer</code></pre>
<h3>Boolean-like Arguments</h3>
<p>Use string values for conditional prompts:</p>
<pre><code class="language-yaml">- flag: --verbose
variable: verbose
default: "no"
description: "Include detailed explanations (yes/no)"</code></pre>
<h2 id="in-prompts">Using in Prompts</h2>
<p>Combine multiple arguments in your prompt template:</p>
<pre><code class="language-yaml">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</code></pre>
<h2 id="best-practices">Best Practices</h2>
<ul>
<li><strong>Use descriptive variable names</strong> - <code>target_language</code> not <code>tl</code></li>
<li><strong>Provide sensible defaults</strong> - Tools should work without any flags</li>
<li><strong>Document choices</strong> - List valid options in the description</li>
<li><strong>Keep flags short</strong> - Use <code>--lang</code> not <code>--target-language</code></li>
</ul>
""",
"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": """
<p class="lead">Complex tools can chain multiple steps together. Each step's output becomes available
to subsequent steps.</p>
<h2 id="step-flow">How Steps Flow</h2>
<pre><code class="language-yaml">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}"</code></pre>
<p>Variables flow through the pipeline:</p>
<ul>
<li><code>{input}</code> available in all steps</li>
<li><code>{key_points}</code> available after step 1</li>
<li><code>{summary}</code> available after step 2</li>
</ul>
<h2 id="mixed-steps">Mixing Prompt and Code Steps</h2>
<p>Combine AI calls with Python processing:</p>
<pre><code class="language-yaml">steps:
# Step 1: AI extracts data
- type: prompt
provider: claude
prompt: |
Extract all email addresses from this text as a comma-separated list:
{input}
output_var: emails_raw
# Step 2: Python cleans the data
- 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
# Step 3: AI formats output
- type: prompt
provider: claude
prompt: |
Format these {email_count} emails as a nice list:
{cleaned_emails}
output_var: formatted
output: "{formatted}"</code></pre>
<h2 id="error-handling">Step Dependencies</h2>
<p>If any step fails, execution stops. Design steps to handle edge cases:</p>
<pre><code class="language-yaml">steps:
- type: code
code: |
# Handle empty input gracefully
if not input.strip():
result = "No input provided"
skip_ai = "yes"
else:
result = input
skip_ai = "no"
output_var: result, skip_ai
- type: prompt
provider: claude
prompt: |
{result}
# AI prompt only runs if skip_ai is "no"
output_var: ai_response</code></pre>
<h2 id="common-patterns">Common Patterns</h2>
<h3>Extract Transform Format</h3>
<pre><code class="language-yaml">steps:
- type: prompt # Extract structured data
- type: code # Transform/filter
- type: prompt # Format for output</code></pre>
<h3>Analyze Synthesize</h3>
<pre><code class="language-yaml">steps:
- type: prompt # Break down into parts
- type: prompt # Combine insights</code></pre>
<h3>Validate Process</h3>
<pre><code class="language-yaml">steps:
- type: code # Validate input format
- type: prompt # Process if valid</code></pre>
<h2 id="debugging">Debugging Multi-Step Tools</h2>
<pre><code class="language-bash"># Show prompts without running
cat test.txt | my-tool --dry-run
# See verbose output
cat test.txt | my-tool --verbose</code></pre>
""",
"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"),
],
},
"code-steps": {
"title": "Code Steps",
"description": "Add Python code processing between AI calls",
"content": """
<p class="lead">Code steps let you run Python code to process data, validate input, or transform
AI outputs between prompts.</p>
<h2 id="basic-syntax">Basic Syntax</h2>
<pre><code class="language-yaml">steps:
- type: code
code: |
# Python code here
result = input.upper()
output_var: result</code></pre>
<h2 id="available-variables">Available Variables</h2>
<p>Code steps have access to:</p>
<ul>
<li><code>input</code> - The original input text</li>
<li>All argument variables</li>
<li>Output variables from previous steps</li>
</ul>
<pre><code class="language-yaml">arguments:
- flag: --max
variable: max_items
default: "10"
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</code></pre>
<h2 id="multiple-outputs">Multiple Output Variables</h2>
<p>Return multiple values with comma-separated output_var:</p>
<pre><code class="language-yaml">- 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</code></pre>
<h2 id="common-operations">Common Operations</h2>
<h3>Text Processing</h3>
<pre><code class="language-yaml">- type: code
code: |
# Remove empty lines
lines = [l for l in input.split('\\n') if l.strip()]
cleaned = '\\n'.join(lines)
output_var: cleaned</code></pre>
<h3>JSON Parsing</h3>
<pre><code class="language-yaml">- type: code
code: |
import json
data = json.loads(ai_response)
formatted = json.dumps(data, indent=2)
output_var: formatted</code></pre>
<h3>Data Validation</h3>
<pre><code class="language-yaml">- 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</code></pre>
<h3>File Operations</h3>
<pre><code class="language-yaml">- 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</code></pre>
<h2 id="using-imports">Using Imports</h2>
<p>Standard library imports work in code steps:</p>
<pre><code class="language-yaml">- type: code
code: |
import json
import re
from datetime import datetime
from pathlib import Path
timestamp = datetime.now().isoformat()
result = f"Processed at {timestamp}"
output_var: result</code></pre>
<h2 id="error-handling">Error Handling</h2>
<p>Handle exceptions to prevent tool failures:</p>
<pre><code class="language-yaml">- type: code
code: |
import json
try:
data = json.loads(ai_response)
result = data.get('summary', 'No summary found')
except json.JSONDecodeError:
result = ai_response # Fall back to raw response
output_var: result</code></pre>
<h2 id="security">Security Notes</h2>
<ul>
<li>Code runs with your user permissions</li>
<li>Don't use <code>eval()</code> on untrusted input</li>
<li>Be careful with file operations</li>
<li>Third-party packages must be installed separately</li>
</ul>
""",
"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"),
],
},
"advanced-workflows": {
"title": "Advanced Workflows",
"description": "Complex multi-provider and advanced tool patterns",
"content": """
<p class="lead">Take your tools to the next level with advanced patterns like multi-provider
workflows, dynamic prompts, and complex data pipelines.</p>
<h2 id="multi-provider">Multi-Provider Workflows</h2>
<p>Use different AI providers for different tasks:</p>
<pre><code class="language-yaml">steps:
# Fast model for extraction
- type: prompt
provider: opencode-grok
prompt: "Extract key facts from: {input}"
output_var: facts
# Powerful model for synthesis
- type: prompt
provider: claude-opus
prompt: |
Create a comprehensive analysis from these facts:
{facts}
output_var: analysis</code></pre>
<h2 id="conditional-logic">Conditional Logic with Code</h2>
<p>Use code steps to implement branching:</p>
<pre><code class="language-yaml">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
# Different prompt based on type
- type: prompt
provider: claude
prompt: |
This is {input_type} data. Analyze it appropriately:
{processed}
output_var: result</code></pre>
<h2 id="iterative-refinement">Iterative Refinement</h2>
<p>Multiple passes for quality improvement:</p>
<pre><code class="language-yaml">steps:
# First draft
- type: prompt
provider: opencode-deepseek
prompt: "Write a summary of: {input}"
output_var: draft
# Critique
- type: prompt
provider: claude-haiku
prompt: |
Review this summary for accuracy and clarity.
List specific improvements needed:
{draft}
output_var: critique
# Final version
- type: prompt
provider: claude-sonnet
prompt: |
Improve this summary based on the feedback:
Original: {draft}
Feedback: {critique}
output_var: final</code></pre>
<h2 id="data-pipelines">Data Processing Pipelines</h2>
<pre><code class="language-yaml">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
- type: prompt
provider: claude
prompt: |
Analyze this CSV data:
- Columns: {headers}
- Row count: {row_count}
- Sample rows: {sample}
Provide insights about the data structure and patterns.
output_var: analysis
# Generate code
- type: prompt
provider: claude
prompt: |
Based on this analysis: {analysis}
Write Python code to process this CSV and extract key metrics.
output_var: code</code></pre>
<h2 id="template-composition">Template Composition</h2>
<p>Build prompts dynamically:</p>
<pre><code class="language-yaml">arguments:
- flag: --task
variable: task
default: "summarize"
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:"
}
instruction = templates.get(task, templates["summarize"])
output_var: instruction
- type: prompt
provider: claude
prompt: |
{instruction}
{input}
output_var: result</code></pre>
<h2 id="external-tools">Integrating External Tools</h2>
<pre><code class="language-yaml">steps:
# Use code to call external commands
- 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
output_var: lint_output
# AI interprets results
- type: prompt
provider: claude
prompt: |
Explain these linting results in plain English
and suggest fixes:
{lint_output}
output_var: explanation</code></pre>
<h2 id="performance-tips">Performance Tips</h2>
<ul>
<li><strong>Use fast models for simple tasks</strong> - grok for extraction, haiku for formatting</li>
<li><strong>Minimize API calls</strong> - Combine related tasks in one prompt</li>
<li><strong>Cache with code steps</strong> - Store intermediate results</li>
<li><strong>Parallel execution</strong> - See <a href="/docs/parallel-orchestration">Parallel Orchestration</a></li>
</ul>
""",
"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"),
],
},
} }
@ -624,8 +1261,15 @@ def get_toc():
SimpleNamespace(slug="getting-started", title="Getting Started", children=[ SimpleNamespace(slug="getting-started", title="Getting Started", children=[
SimpleNamespace(slug="installation", title="Installation"), SimpleNamespace(slug="installation", title="Installation"),
SimpleNamespace(slug="first-tool", title="Your First Tool"), SimpleNamespace(slug="first-tool", title="Your First Tool"),
SimpleNamespace(slug="yaml-config", title="YAML Config"),
]),
SimpleNamespace(slug="arguments", title="Custom Arguments", children=[]),
SimpleNamespace(slug="multi-step", title="Multi-Step Workflows", children=[
SimpleNamespace(slug="code-steps", title="Code Steps"),
]), ]),
SimpleNamespace(slug="publishing", title="Publishing", children=[]),
SimpleNamespace(slug="providers", title="Providers", children=[]), SimpleNamespace(slug="providers", title="Providers", children=[]),
SimpleNamespace(slug="parallel-orchestration", title="Parallel Orchestration", children=[]), SimpleNamespace(slug="publishing", title="Publishing", children=[]),
SimpleNamespace(slug="advanced-workflows", title="Advanced Workflows", children=[
SimpleNamespace(slug="parallel-orchestration", title="Parallel Orchestration"),
]),
] ]

View File

@ -528,6 +528,14 @@ def tutorials():
@web_bp.route("/tutorials/<path:path>", endpoint="tutorials_path") @web_bp.route("/tutorials/<path:path>", endpoint="tutorials_path")
def tutorials_path(path: str): def tutorials_path(path: str):
from .docs_content import get_doc
doc = get_doc(path)
if doc:
return render_template(
"pages/docs.html",
doc=doc,
toc=None, # No sidebar for tutorial pages
)
return render_template( return render_template(
"pages/content.html", "pages/content.html",
title=_title_case(path), title=_title_case(path),