CmdForge/src/cmdforge/web/docs_content.py

2447 lines
83 KiB
Python

"""Documentation content for CmdForge web UI.
This module contains the actual documentation text that gets rendered
on the /docs pages. Content is stored as markdown-ish HTML for simplicity.
"""
DOCS = {
"getting-started": {
"title": "Getting Started",
"description": "Learn how to install CmdForge and create your first AI-powered CLI tool",
"content": """
<p class="lead">CmdForge lets you build custom AI-powered CLI commands using simple YAML configuration.
Create tools that work with any AI provider and compose them like Unix pipes.</p>
<h2 id="what-is-cmdforge">What is CmdForge?</h2>
<p>CmdForge is a lightweight personal tool builder that lets you:</p>
<ul>
<li><strong>Create custom CLI commands</strong> that call AI providers</li>
<li><strong>Chain prompts with Python code</strong> for complex workflows</li>
<li><strong>Use tools like Unix pipes</strong> - read from stdin, write to stdout</li>
<li><strong>Share and discover tools</strong> through the registry</li>
</ul>
<h2 id="quick-start">Quick Start</h2>
<p>Get up and running in under a minute:</p>
<pre><code class="language-bash"># Install CmdForge
pip install cmdforge
# Create your first tool (choose your style)
cmdforge # Desktop application with visual builder
cmdforge create # CLI wizard
# Or install a tool from the registry
cmdforge registry install official/summarize
# Use it!
cat article.txt | summarize</code></pre>
<div class="bg-cyan-50 border-l-4 border-cyan-500 p-4 my-4">
<p class="font-semibold text-cyan-800">Two Ways to Build</p>
<p class="text-cyan-700"><code>cmdforge</code> launches the desktop application with a visual builder.
<code>cmdforge create</code> uses a command-line wizard. Both create the same YAML config files.</p>
</div>
<h2 id="how-it-works">How It Works</h2>
<p>Each tool is a YAML file that defines:</p>
<ol>
<li><strong>Arguments</strong> - Custom flags your tool accepts</li>
<li><strong>Steps</strong> - Prompts to send to AI or Python code to run</li>
<li><strong>Output</strong> - How to format the final result</li>
</ol>
<p>Here's a simple example:</p>
<pre><code class="language-yaml">name: summarize
version: "1.0.0"
description: Summarize text using AI
arguments:
- flag: --max-length
variable: max_length
default: "200"
description: Maximum summary length in words
steps:
- type: prompt
provider: claude
prompt: |
Summarize the following text in {max_length} words or less:
{input}
output_var: summary
output: "{summary}"</code></pre>
<h2 id="next-steps">Next Steps</h2>
<ul>
<li><a href="/docs/installation">Installation Guide</a> - Detailed setup instructions</li>
<li><a href="/docs/visual-builder">Visual Builder</a> - Build tools visually (no YAML required)</li>
<li><a href="/docs/first-tool">Your First Tool</a> - Step-by-step YAML tutorial</li>
<li><a href="/docs/providers">Providers</a> - Configure AI providers</li>
<li><a href="/tools">Browse Tools</a> - Discover community tools</li>
</ul>
<h2 id="get-help">Get Help</h2>
<p>Stuck? Have questions? We've got you covered:</p>
<ul>
<li><a href="/forum">Community Forum</a> - Ask questions, share projects, and connect with other users</li>
<li><a href="/forum/c/help">Help &amp; Support</a> - Get help with installation, configuration, or usage</li>
<li><a href="https://gitea.brrd.tech/rob/CmdForge/issues" target="_blank">Report a Bug</a> - Found an issue? Let us know</li>
</ul>
""",
"headings": [
("what-is-cmdforge", "What is CmdForge?"),
("quick-start", "Quick Start"),
("how-it-works", "How It Works"),
("next-steps", "Next Steps"),
("get-help", "Get Help"),
],
},
"installation": {
"title": "Installation",
"description": "How to install CmdForge on your system",
"parent": "getting-started",
"content": """
<p class="lead">CmdForge requires Python 3.8+ and works on Linux, macOS, and Windows.</p>
<h2 id="pip-install">Install with pip</h2>
<p>The simplest way to install CmdForge:</p>
<pre><code class="language-bash">pip install cmdforge</code></pre>
<p>Or with pipx for isolated installation:</p>
<pre><code class="language-bash">pipx install cmdforge</code></pre>
<h2 id="verify">Verify Installation</h2>
<pre><code class="language-bash">cmdforge --version
cmdforge --help</code></pre>
<h2 id="configure-provider">Configure a Provider</h2>
<p>CmdForge needs at least one AI provider configured. The easiest is Claude CLI:</p>
<pre><code class="language-bash"># Install Claude CLI (if you have an Anthropic API key)
pip install claude-cli
# Or use OpenAI
pip install openai
# Configure your provider
cmdforge config</code></pre>
<h2 id="wrapper-scripts">Wrapper Scripts Location</h2>
<p>CmdForge installs wrapper scripts to <code>~/.local/bin/</code>. Make sure this is in your PATH:</p>
<pre><code class="language-bash"># Add to ~/.bashrc or ~/.zshrc
export PATH="$HOME/.local/bin:$PATH"</code></pre>
<h2 id="development-install">Development Installation</h2>
<p>To contribute or modify CmdForge:</p>
<pre><code class="language-bash">git clone https://gitea.brrd.tech/rob/CmdForge.git
cd CmdForge
pip install -e ".[dev]"</code></pre>
""",
"headings": [
("pip-install", "Install with pip"),
("verify", "Verify Installation"),
("configure-provider", "Configure a Provider"),
("wrapper-scripts", "Wrapper Scripts Location"),
("development-install", "Development Installation"),
],
},
"first-tool": {
"title": "Your First Tool",
"description": "Create your first CmdForge command step by step",
"parent": "getting-started",
"content": """
<p class="lead">Let's create a simple tool that explains code. You'll learn the basics of tool configuration.</p>
<h2 id="create-tool">Create the Tool</h2>
<p>Run the interactive creator:</p>
<pre><code class="language-bash">cmdforge create</code></pre>
<p>Or create the file manually at <code>~/.cmdforge/explain/config.yaml</code>:</p>
<pre><code class="language-yaml">name: explain
version: "1.0.0"
description: Explain code or concepts in simple terms
category: code
arguments:
- flag: --level
variable: level
default: "beginner"
description: "Explanation level: beginner, intermediate, or expert"
steps:
- type: prompt
provider: claude
prompt: |
Explain the following in simple terms suitable for a {level}:
{input}
Be concise but thorough. Use examples where helpful.
output_var: explanation
output: "{explanation}"</code></pre>
<h2 id="test-it">Test Your Tool</h2>
<pre><code class="language-bash"># Explain some code
echo "def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)" | explain
# Explain for an expert
cat complex_algorithm.py | explain --level expert</code></pre>
<h2 id="understanding-config">Understanding the Config</h2>
<h3>Arguments</h3>
<p>Each argument becomes a CLI flag. The <code>variable</code> name is used in templates:</p>
<pre><code class="language-yaml">arguments:
- flag: --level # CLI flag: --level beginner
variable: level # Use as {level} in prompts
default: "beginner" # Default if not specified</code></pre>
<h3>Steps</h3>
<p>Steps run in order. Each step can be a prompt or Python code:</p>
<pre><code class="language-yaml">steps:
- type: prompt
provider: claude # Which AI to use
prompt: "..." # The prompt template
output_var: result # Store response in {result}</code></pre>
<h3>Output</h3>
<p>The output template formats the final result:</p>
<pre><code class="language-yaml">output: "{explanation}" # Print the explanation variable</code></pre>
<h2 id="next">Next Steps</h2>
<ul>
<li><a href="/docs/publishing">Publish your tool</a> to the registry</li>
<li>Learn about <a href="/docs/providers">different providers</a></li>
<li>See <a href="/tools">example tools</a> for inspiration</li>
</ul>
""",
"headings": [
("create-tool", "Create the Tool"),
("test-it", "Test Your Tool"),
("understanding-config", "Understanding the Config"),
("next", "Next Steps"),
],
},
"publishing": {
"title": "Publishing Tools",
"description": "Share your tools with the CmdForge community",
"content": """
<p class="lead">Share your tools with the community by publishing to the CmdForge Registry.</p>
<h2 id="before-publishing">Before Publishing</h2>
<p>Make sure your tool has:</p>
<ul>
<li>A descriptive <code>name</code> and <code>description</code></li>
<li>A proper <code>version</code> (semver format: 1.0.0)</li>
<li>A <code>README.md</code> file with usage examples</li>
<li>Tested and working functionality</li>
</ul>
<h2 id="create-account">Create an Account</h2>
<p>Register at <a href="/register">the registry</a> to get your publisher namespace.</p>
<h2 id="connect-account">Connect Your Account</h2>
<p>Link your CLI or GUI to your registry account with a simple pairing flow:</p>
<h3>Option 1: From the Command Line</h3>
<pre><code class="language-bash"># Start the connection flow
cmdforge config connect yourusername</code></pre>
<p>This opens your browser to approve the connection. Once approved, you're ready to publish!</p>
<h3>Option 2: From the Desktop GUI</h3>
<ol>
<li>Launch <code>cmdforge</code> (the desktop app)</li>
<li>Go to the <strong>Registry</strong> page</li>
<li>Click the <strong>Connect</strong> button</li>
<li>Enter your username and approve in the browser</li>
</ol>
<div class="bg-cyan-50 border-l-4 border-cyan-500 p-4 my-4">
<p class="font-semibold text-cyan-800">No More Tokens!</p>
<p class="text-cyan-700">The connection flow replaces the old API token system. Your devices stay
connected until you disconnect them from your <a href="/dashboard/connections">Dashboard → Connections</a> page.</p>
</div>
<h2 id="publish">Publish Your Tool</h2>
<pre><code class="language-bash"># Publish from CLI
cmdforge registry publish mytool
# Dry run to validate without publishing
cmdforge registry publish mytool --dry-run
# Check your published tools
cmdforge registry my-tools</code></pre>
<p>Or use the desktop GUI:</p>
<ol>
<li>Open the <strong>My Tools</strong> page</li>
<li>Right-click on your tool and select <strong>Publish</strong></li>
<li>Confirm the version and publish</li>
</ol>
<h2 id="moderation">Moderation Process</h2>
<p>Submitted tools go through a brief moderation review to ensure quality:</p>
<ul>
<li><strong>Pending</strong> - Your tool is in the queue for review</li>
<li><strong>Approved</strong> - Your tool is live in the registry</li>
<li><strong>Changes Requested</strong> - A moderator has feedback for you</li>
</ul>
<p>Check your moderation status anytime:</p>
<pre><code class="language-bash"># See status of your submissions
cmdforge registry status
# Sync moderator feedback to your local tool
cmdforge registry status --sync</code></pre>
<h2 id="versioning">Versioning</h2>
<p>Published versions are <strong>immutable</strong>. To update a tool:</p>
<ol>
<li>Make your changes</li>
<li>Bump the version in <code>config.yaml</code></li>
<li>Run <code>cmdforge registry publish mytool</code></li>
</ol>
<h2 id="best-practices">Best Practices</h2>
<ul>
<li><strong>Clear names</strong> - Use descriptive, lowercase names with hyphens</li>
<li><strong>Good descriptions</strong> - Explain what the tool does in one sentence</li>
<li><strong>Document arguments</strong> - Describe each flag in the help text</li>
<li><strong>Include examples</strong> - Show real usage in your README</li>
<li><strong>Choose the right category</strong> - Help users discover your tool</li>
<li><strong>Respond to feedback</strong> - If changes are requested, address them promptly</li>
</ul>
""",
"headings": [
("before-publishing", "Before Publishing"),
("create-account", "Create an Account"),
("connect-account", "Connect Your Account"),
("publish", "Publish Your Tool"),
("moderation", "Moderation Process"),
("versioning", "Versioning"),
("best-practices", "Best Practices"),
],
},
"providers": {
"title": "AI Providers",
"description": "Configure different AI providers for your tools",
"content": """
<p class="lead">CmdForge works with any AI provider that has a CLI interface. Configure providers in
<code>~/.cmdforge/providers.yaml</code>.</p>
<h2 id="provider-config">Provider Configuration</h2>
<p>Create or edit <code>~/.cmdforge/providers.yaml</code>:</p>
<pre><code class="language-yaml">providers:
- name: claude
command: "claude -p"
- name: openai
command: "openai-cli"
- name: ollama
command: "ollama run llama2"
- name: mock
command: "echo '[MOCK RESPONSE]'"</code></pre>
<h2 id="using-providers">Using Providers in Tools</h2>
<p>Specify the provider in your step:</p>
<pre><code class="language-yaml">steps:
- type: prompt
provider: claude # Uses the "claude" provider from config
prompt: "..."
output_var: response</code></pre>
<h2 id="popular-providers">Popular Providers</h2>
<h3>Claude (Anthropic)</h3>
<pre><code class="language-bash"># Install
pip install claude-cli
# Configure with your API key
export ANTHROPIC_API_KEY="sk-ant-..."</code></pre>
<pre><code class="language-yaml"># providers.yaml
providers:
- name: claude
command: "claude -p"</code></pre>
<h3>OpenAI</h3>
<pre><code class="language-bash"># Install
pip install openai-cli
# Configure
export OPENAI_API_KEY="sk-..."</code></pre>
<h3>Ollama (Local)</h3>
<pre><code class="language-bash"># Install Ollama from ollama.ai
# Pull a model
ollama pull llama2</code></pre>
<pre><code class="language-yaml"># providers.yaml
providers:
- name: ollama
command: "ollama run llama2"</code></pre>
<h2 id="testing">Testing with Mock Provider</h2>
<p>Use the mock provider to test tools without API calls:</p>
<pre><code class="language-yaml">providers:
- name: mock
command: "echo 'This is a mock response for testing'"</code></pre>
<h2 id="provider-selection">Choosing a Provider</h2>
<table class="table">
<thead>
<tr>
<th>Provider</th>
<th>Best For</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td>Claude</td>
<td>Complex reasoning, long context</td>
<td>Pay per token</td>
</tr>
<tr>
<td>OpenAI</td>
<td>General purpose, fast</td>
<td>Pay per token</td>
</tr>
<tr>
<td>Ollama</td>
<td>Privacy, offline use</td>
<td>Free (local)</td>
</tr>
</tbody>
</table>
""",
"headings": [
("provider-config", "Provider Configuration"),
("using-providers", "Using Providers in Tools"),
("popular-providers", "Popular Providers"),
("testing", "Testing with Mock Provider"),
("provider-selection", "Choosing a Provider"),
],
},
"parallel-orchestration": {
"title": "Parallel Orchestration",
"description": "Run multiple CmdForge concurrently for faster workflows",
"content": """
<p class="lead">CmdForge executes steps sequentially within a tool, but you can run
<strong>multiple tools in parallel</strong> using Python's ThreadPoolExecutor. This pattern
is ideal for multi-agent workflows, parallel analysis, or any task where you need responses
from multiple AI providers simultaneously.</p>
<h2 id="why-parallel">Why Parallel Execution?</h2>
<p>Consider a code review workflow that needs input from multiple perspectives:</p>
<ul>
<li><strong>Sequential</strong>: Security → Performance → Style = 45 seconds</li>
<li><strong>Parallel</strong>: All three at once = 15 seconds</li>
</ul>
<h2 id="basic-pattern">Basic Pattern</h2>
<p>Use Python's <code>concurrent.futures</code> to run multiple CmdForge in parallel:</p>
<pre><code class="language-python">import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
def run_tool(tool_name: str, input_text: str) -> dict:
\"\"\"Run a SmartTool and return its output.\"\"\"
result = subprocess.run(
[tool_name],
input=input_text,
capture_output=True,
text=True
)
return {
"tool": tool_name,
"output": result.stdout,
"success": result.returncode == 0
}
def run_parallel(tools: list[str], input_text: str) -> list[dict]:
\"\"\"Run multiple tools in parallel on the same input.\"\"\"
results = []
with ThreadPoolExecutor(max_workers=len(tools)) as executor:
# Submit all tools
futures = {
executor.submit(run_tool, tool, input_text): tool
for tool in tools
}
# Collect results as they complete
for future in as_completed(futures):
results.append(future.result())
return results
# Example usage
tools = ["security-review", "performance-review", "style-review"]
code = open("main.py").read()
reviews = run_parallel(tools, code)
for review in reviews:
print(f"=== {review['tool']} ===")
print(review['output'])
</code></pre>
<h2 id="real-world-example">Real-World Example: Multi-Perspective Analysis</h2>
<p>Here's a complete script that gets multiple AI perspectives on a topic:</p>
<pre><code class="language-python">#!/usr/bin/env python3
\"\"\"Get multiple AI perspectives on a topic in parallel.\"\"\"
import subprocess
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
# Define your perspective tools (each is a SmartTool)
PERSPECTIVES = [
"perspective-optimist", # Focuses on opportunities
"perspective-critic", # Identifies problems
"perspective-pragmatist", # Focuses on actionability
]
def get_perspective(tool: str, topic: str) -> dict:
\"\"\"Get one perspective on a topic.\"\"\"
result = subprocess.run(
[tool],
input=topic,
capture_output=True,
text=True,
timeout=60 # Timeout after 60 seconds
)
return {
"perspective": tool.replace("perspective-", ""),
"response": result.stdout.strip(),
"success": result.returncode == 0
}
def analyze_topic(topic: str) -> list[dict]:
\"\"\"Get all perspectives in parallel.\"\"\"
with ThreadPoolExecutor(max_workers=len(PERSPECTIVES)) as executor:
futures = {
executor.submit(get_perspective, tool, topic): tool
for tool in PERSPECTIVES
}
results = []
for future in as_completed(futures):
try:
results.append(future.result())
except Exception as e:
tool = futures[future]
results.append({
"perspective": tool,
"response": f"Error: {e}",
"success": False
})
return results
if __name__ == "__main__":
import sys
topic = sys.stdin.read() if not sys.stdin.isatty() else input("Topic: ")
print("Gathering perspectives...\\n")
perspectives = analyze_topic(topic)
for p in perspectives:
status = "" if p["success"] else ""
print(f"[{status}] {p['perspective'].upper()}")
print("-" * 40)
print(p["response"])
print()
</code></pre>
<h2 id="with-progress">Adding Progress Feedback</h2>
<p>For long-running parallel tasks, show progress as tools complete:</p>
<pre><code class="language-python">import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
def run_with_progress(tools: list[str], input_text: str):
\"\"\"Run tools in parallel with progress updates.\"\"\"
total = len(tools)
completed = 0
with ThreadPoolExecutor(max_workers=total) as executor:
futures = {
executor.submit(run_tool, tool, input_text): tool
for tool in tools
}
results = []
for future in as_completed(futures):
completed += 1
tool = futures[future]
result = future.result()
results.append(result)
# Progress update
status = "" if result["success"] else ""
print(f"[{completed}/{total}] {status} {tool}", file=sys.stderr)
return results
</code></pre>
<h2 id="error-handling">Error Handling</h2>
<p>Handle failures gracefully so one tool doesn't break the entire workflow:</p>
<pre><code class="language-python">def run_tool_safe(tool_name: str, input_text: str, timeout: int = 120) -> dict:
\"\"\"Run a tool with timeout and error handling.\"\"\"
try:
result = subprocess.run(
[tool_name],
input=input_text,
capture_output=True,
text=True,
timeout=timeout
)
return {
"tool": tool_name,
"output": result.stdout,
"error": result.stderr if result.returncode != 0 else None,
"success": result.returncode == 0
}
except subprocess.TimeoutExpired:
return {
"tool": tool_name,
"output": "",
"error": f"Timeout after {timeout}s",
"success": False
}
except FileNotFoundError:
return {
"tool": tool_name,
"output": "",
"error": f"Tool '{tool_name}' not found",
"success": False
}
</code></pre>
<h2 id="best-practices">Best Practices</h2>
<ul>
<li><strong>Set timeouts</strong> - Prevent hanging on slow providers</li>
<li><strong>Handle errors per-tool</strong> - Don't let one failure break everything</li>
<li><strong>Limit concurrency</strong> - Match <code>max_workers</code> to your use case</li>
<li><strong>Use stderr for progress</strong> - Keep stdout clean for piping</li>
<li><strong>Consider rate limits</strong> - Some providers limit concurrent requests</li>
</ul>
<h2 id="example-project">Full Example: orchestrated-discussions</h2>
<p>For a complete implementation of parallel CmdForge orchestration, see the
<a href="https://gitea.brrd.tech/rob/orchestrated-discussions" target="_blank">orchestrated-discussions</a>
project. It implements:</p>
<ul>
<li>Multiple AI "participants" as CmdForge</li>
<li>Parallel execution with live progress logging</li>
<li>Shared log files for real-time monitoring</li>
<li>Discussion workflows with voting and consensus</li>
</ul>
""",
"headings": [
("why-parallel", "Why Parallel Execution?"),
("basic-pattern", "Basic Pattern"),
("real-world-example", "Real-World Example"),
("with-progress", "Adding Progress Feedback"),
("error-handling", "Error Handling"),
("best-practices", "Best Practices"),
("example-project", "Full Example Project"),
],
},
"yaml-config": {
"title": "The Anatomy of a SmartTool",
"description": "Master the YAML configuration that powers every SmartTool",
"content": """
<p class="lead">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.</p>
<div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 my-6">
<p class="font-semibold text-indigo-800">What You'll Learn</p>
<ul class="mt-2 text-indigo-700">
<li>The 5 essential parts of every tool config</li>
<li>How variables flow through your tool like water through pipes</li>
<li>The one YAML gotcha that trips up everyone (and how to avoid it)</li>
</ul>
</div>
<h2 id="the-simplest-tool">The Simplest Tool That Actually Works</h2>
<p>Let's start with something real. Here's a complete, working tool in just 8 lines:</p>
<pre><code class="language-yaml">name: shout
version: "1.0.0"
description: Makes text LOUD
steps:
- type: prompt
provider: claude
prompt: "Convert this to ALL CAPS with enthusiasm: {input}"
output_var: result
output: "{result}"</code></pre>
<p>Save this to <code>~/.cmdforge/shout/config.yaml</code> and you've got a working command:</p>
<pre><code class="language-bash">$ echo "hello world" | shout
HELLO WORLD!!!</code></pre>
<p><strong>That's it.</strong> Everything else is just adding features to this basic pattern.</p>
<h2 id="the-five-parts">The Five Parts of Every Tool</h2>
<p>Think of a SmartTool config like a recipe card:</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-6">
<div class="bg-white border rounded-lg p-4">
<p class="font-bold text-gray-900">1. Identity</p>
<p class="text-sm text-gray-600">name, version, description</p>
<p class="text-xs text-gray-500 mt-1">Who is this tool?</p>
</div>
<div class="bg-white border rounded-lg p-4">
<p class="font-bold text-gray-900">2. Arguments</p>
<p class="text-sm text-gray-600">Custom flags like --format</p>
<p class="text-xs text-gray-500 mt-1">What options does it accept?</p>
</div>
<div class="bg-white border rounded-lg p-4">
<p class="font-bold text-gray-900">3. Steps</p>
<p class="text-sm text-gray-600">Prompts and code blocks</p>
<p class="text-xs text-gray-500 mt-1">What does it do?</p>
</div>
<div class="bg-white border rounded-lg p-4">
<p class="font-bold text-gray-900">4. Output</p>
<p class="text-sm text-gray-600">The final template</p>
<p class="text-xs text-gray-500 mt-1">What comes out?</p>
</div>
</div>
<p>Let's see all five in action:</p>
<pre><code class="language-yaml"># 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: --lang
variable: language
default: "Spanish"
description: Target language
# 3. STEPS - The actual work
steps:
- type: prompt
provider: claude
prompt: |
Translate this text to {language}:
{input}
output_var: translation
# 4. OUTPUT - What comes out the other end
output: "{translation}"</code></pre>
<h2 id="variables-are-pipes">Variables Are Pipes</h2>
<p>Here's the mental model that makes everything click: <strong>variables are pipes that carry data through your tool.</strong></p>
<pre><code class="language-bash"># The user runs:
echo "Hello" | translate --lang French</code></pre>
<p>Inside your tool, three pipes are now flowing:</p>
<table class="min-w-full my-4">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left">Variable</th>
<th class="px-4 py-2 text-left">Contains</th>
<th class="px-4 py-2 text-left">Source</th>
</tr>
</thead>
<tbody>
<tr class="border-b">
<td class="px-4 py-2"><code>{input}</code></td>
<td class="px-4 py-2">"Hello"</td>
<td class="px-4 py-2">Piped in from stdin</td>
</tr>
<tr class="border-b">
<td class="px-4 py-2"><code>{language}</code></td>
<td class="px-4 py-2">"French"</td>
<td class="px-4 py-2">From --lang flag</td>
</tr>
<tr>
<td class="px-4 py-2"><code>{translation}</code></td>
<td class="px-4 py-2">"Bonjour"</td>
<td class="px-4 py-2">Created by the AI step</td>
</tr>
</tbody>
</table>
<p>You can use any variable in any step that comes <em>after</em> it's created. They flow downstream, never up.</p>
<h2 id="the-yaml-trap">The YAML Trap Everyone Falls Into</h2>
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 my-6">
<p class="font-semibold text-amber-800">Warning: Unquoted Numbers</p>
<p class="text-amber-700">YAML is <em>helpful</em> in ways that will hurt you. Watch out for versions:</p>
</div>
<pre><code class="language-yaml"># 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"</code></pre>
<p>Always quote your version numbers. Always. Even if they look fine. Future you will thank present you.</p>
<h2 id="try-it">Try It Yourself</h2>
<div class="bg-green-50 border-l-4 border-green-500 p-4 my-6">
<p class="font-semibold text-green-800">Exercise: Build a Tone Shifter</p>
<p class="text-green-700">Create a tool that rewrites text in different tones. It should:</p>
<ol class="mt-2 text-green-700 list-decimal list-inside">
<li>Accept a <code>--tone</code> flag (default: "professional")</li>
<li>Rewrite the input in that tone</li>
<li>Output just the rewritten text</li>
</ol>
</div>
<details class="my-4">
<summary class="cursor-pointer text-indigo-600 font-medium">Click to see the solution</summary>
<pre class="mt-2"><code class="language-yaml">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
output: "{result}"</code></pre>
</details>
<h2 id="naming-rules">Naming Your Tools</h2>
<p>Tool names become Unix commands, so they follow Unix conventions:</p>
<pre><code class="language-yaml"># 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</code></pre>
<p>Pick names that complete the sentence: <em>"I need to _____ this file."</em></p>
<h2 id="where-to-next">Where To Next?</h2>
<p>You now understand the structure. Time to add superpowers:</p>
<ul>
<li><a href="/tutorials/arguments">Custom Arguments</a> - Add flags like a pro</li>
<li><a href="/tutorials/multi-step">Multi-Step Workflows</a> - Chain AI calls together</li>
<li><a href="/tutorials/code-steps">Code Steps</a> - Mix Python into your tools</li>
</ul>
""",
"headings": [
("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": "Adding Knobs and Switches",
"description": "Give your tools superpowers with custom flags",
"content": """
<p class="lead">The difference between a good tool and a great tool? Options. Let's add
<code>--lang french</code> and <code>--format json</code> to your toolkit.</p>
<div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 my-6">
<p class="font-semibold text-indigo-800">What You'll Build</p>
<p class="text-indigo-700">A flexible summarizer with adjustable length and style options.</p>
</div>
<h2 id="your-first-flag">Your First Flag</h2>
<p>Let's add a <code>--style</code> flag to control how our summary sounds:</p>
<pre><code class="language-yaml">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: |
Summarize this in a {style} style:
{input}
output_var: summary
output: "{summary}"</code></pre>
<p>Now you can run:</p>
<pre><code class="language-bash"># Default (concise)
cat article.txt | summarize
# Detailed analysis
cat article.txt | summarize --style detailed
# Quick bullet points
cat article.txt | summarize --style bullet-points</code></pre>
<p><strong>Notice how the flag value flows right into <code>{style}</code> in the prompt.</strong> That's the magic.</p>
<h2 id="anatomy-of-an-argument">Anatomy of an Argument</h2>
<p>Every argument has four parts:</p>
<pre><code class="language-yaml">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</code></pre>
<div class="bg-cyan-50 border-l-4 border-cyan-500 p-4 my-6">
<p class="font-semibold text-cyan-800">Pro Tip: The Golden Rule</p>
<p class="text-cyan-700">Your tool should work <em>perfectly</em> with zero flags. Defaults are for
the 80% case. Flags are for the other 20%.</p>
</div>
<h2 id="stacking-flags">Stacking Multiple Flags</h2>
<p>Real tools need multiple options. Here's a translation tool with three knobs:</p>
<pre><code class="language-yaml">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: |
Translate to {target_lang}.
Use a {tone} tone.
Preserve the {preserve}.
Text:
{input}
output_var: result
output: "{result}"</code></pre>
<p>Now you have fine-grained control:</p>
<pre><code class="language-bash"># 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</code></pre>
<h2 id="flag-design">The Art of Good Flags</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-6">
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
<p class="font-bold text-green-800 mb-2">Do This</p>
<ul class="text-sm text-green-700 space-y-1">
<li><code>--lang</code> not <code>--target-language</code></li>
<li><code>--max</code> not <code>--maximum-length</code></li>
<li><code>--style</code> not <code>--s</code></li>
</ul>
</div>
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
<p class="font-bold text-red-800 mb-2">Not This</p>
<ul class="text-sm text-red-700 space-y-1">
<li><code>-l</code> (too cryptic)</li>
<li><code>--target_language</code> (underscores feel wrong)</li>
<li><code>--TargetLanguage</code> (this isn't Java)</li>
</ul>
</div>
</div>
<h2 id="numeric-gotcha">The Numeric Gotcha</h2>
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 my-6">
<p class="font-semibold text-amber-800">Warning: Quote Your Numbers!</p>
<p class="text-amber-700">YAML defaults are strings. Always quote numbers:</p>
</div>
<pre><code class="language-yaml"># WRONG - YAML might do weird things
default: 100
# RIGHT - Always a string
default: "100"</code></pre>
<p>In your prompt, it'll work the same either way—but quoting prevents surprises.</p>
<h2 id="try-it">Try It: Build a Code Reviewer</h2>
<div class="bg-green-50 border-l-4 border-green-500 p-4 my-6">
<p class="font-semibold text-green-800">Exercise</p>
<p class="text-green-700">Create a code review tool with these flags:</p>
<ul class="mt-2 text-green-700">
<li><code>--focus</code> - What to focus on (bugs, style, performance)</li>
<li><code>--severity</code> - Minimum severity to report (low, medium, high)</li>
</ul>
</div>
<details class="my-4">
<summary class="cursor-pointer text-indigo-600 font-medium">See the solution</summary>
<pre class="mt-2"><code class="language-yaml">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}"</code></pre>
</details>
<h2 id="next-up">Next Up</h2>
<p>You've mastered single-step tools with arguments. Ready for the real power?</p>
<ul>
<li><a href="/tutorials/multi-step">Multi-Step Workflows</a> - Chain multiple AI calls</li>
<li><a href="/tutorials/code-steps">Code Steps</a> - Add Python processing</li>
</ul>
""",
"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": """
<p class="lead">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.</p>
<div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 my-6">
<p class="font-semibold text-indigo-800">What You'll Learn</p>
<ul class="mt-2 text-indigo-700">
<li>How to pass data between multiple AI steps</li>
<li>Mixing AI calls with Python processing</li>
<li>The three patterns that solve 90% of multi-step problems</li>
</ul>
</div>
<h2 id="your-first-pipeline">Your First Pipeline</h2>
<p>Let's build something real: a tool that extracts key points from an article, then turns them into a tweet thread.</p>
<pre><code class="language-yaml">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}"</code></pre>
<p>The magic is in line 20: <code>{key_points}</code> contains the output from Step 1. Data flows downstream automatically.</p>
<h2 id="the-variable-waterfall">The Variable Waterfall</h2>
<p>Think of variables like water flowing downhill—they only go forward, never back:</p>
<div class="bg-white border rounded-lg p-4 my-6">
<div class="flex items-center justify-between">
<div class="text-center">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto">
<span class="text-2xl">📥</span>
</div>
<p class="mt-2 text-sm font-medium">Input</p>
<p class="text-xs text-gray-500">{input}</p>
</div>
<div class="text-gray-300">→</div>
<div class="text-center">
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto">
<span class="text-2xl">🤖</span>
</div>
<p class="mt-2 text-sm font-medium">Step 1</p>
<p class="text-xs text-gray-500">{key_points}</p>
</div>
<div class="text-gray-300">→</div>
<div class="text-center">
<div class="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mx-auto">
<span class="text-2xl">🤖</span>
</div>
<p class="mt-2 text-sm font-medium">Step 2</p>
<p class="text-xs text-gray-500">{thread}</p>
</div>
<div class="text-gray-300">→</div>
<div class="text-center">
<div class="w-16 h-16 bg-orange-100 rounded-full flex items-center justify-center mx-auto">
<span class="text-2xl">📤</span>
</div>
<p class="mt-2 text-sm font-medium">Output</p>
</div>
</div>
</div>
<p><strong>Step 2 can use:</strong> <code>{input}</code>, <code>{key_points}</code><br>
<strong>Step 2 cannot use:</strong> Variables from Step 3 (doesn't exist yet!)</p>
<h2 id="adding-python-glue">Adding Python Glue</h2>
<p>AI is great at language. Python is great at data wrangling. Together? Unstoppable.</p>
<pre><code class="language-yaml">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
# Python cleans them up
- type: code
code: |
# 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
# AI formats the output nicely
- type: prompt
provider: claude
prompt: |
Format these {count} email addresses as a clean list
with any obvious categorization (personal, work, etc):
{clean_list}
output_var: result
output: "{result}"</code></pre>
<div class="bg-cyan-50 border-l-4 border-cyan-500 p-4 my-6">
<p class="font-semibold text-cyan-800">Pro Tip: Code Steps Return Multiple Variables</p>
<p class="text-cyan-700">Notice <code>output_var: clean_list, count</code>—you can export multiple variables
from a single code step. Just list them comma-separated.</p>
</div>
<h2 id="the-three-patterns">The Three Patterns That Solve Everything</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6">
<div class="bg-white border-2 border-green-200 rounded-lg p-4">
<p class="font-bold text-green-800">Extract → Transform → Format</p>
<pre class="text-xs mt-2 bg-gray-50 p-2 rounded"><code>prompt # Pull out data
code # Clean/filter
prompt # Make it pretty</code></pre>
<p class="text-xs text-gray-600 mt-2">Use for: Data extraction, report generation</p>
</div>
<div class="bg-white border-2 border-blue-200 rounded-lg p-4">
<p class="font-bold text-blue-800">Analyze → Synthesize</p>
<pre class="text-xs mt-2 bg-gray-50 p-2 rounded"><code>prompt # Break it down
prompt # Build it up</code></pre>
<p class="text-xs text-gray-600 mt-2">Use for: Summaries, insights, rewriting</p>
</div>
<div class="bg-white border-2 border-purple-200 rounded-lg p-4">
<p class="font-bold text-purple-800">Validate → Process</p>
<pre class="text-xs mt-2 bg-gray-50 p-2 rounded"><code>code # Check input
prompt # Do the work</code></pre>
<p class="text-xs text-gray-600 mt-2">Use for: Safe processing, error handling</p>
</div>
</div>
<h2 id="when-things-go-wrong">When Things Go Wrong</h2>
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 my-6">
<p class="font-semibold text-amber-800">Warning: Steps Are All-Or-Nothing</p>
<p class="text-amber-700">If Step 2 fails, Step 3 never runs. Design defensively!</p>
</div>
<pre><code class="language-yaml">steps:
# Guard clause in code
- type: code
code: |
if not input.strip():
processed = "ERROR: Empty input"
is_valid = False
else:
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</code></pre>
<h2 id="try-it">Try It: Build a Code Explainer</h2>
<div class="bg-green-50 border-l-4 border-green-500 p-4 my-6">
<p class="font-semibold text-green-800">Exercise</p>
<p class="text-green-700">Build a tool that:</p>
<ol class="mt-2 text-green-700 list-decimal list-inside">
<li>Identifies the programming language</li>
<li>Explains what the code does (using the language info)</li>
<li>Suggests improvements</li>
</ol>
</div>
<details class="my-4">
<summary class="cursor-pointer text-indigo-600 font-medium">See the solution</summary>
<pre class="mt-2"><code class="language-yaml">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: |
Explain this {language} code in plain English.
Describe what each part does.
{input}
output_var: explanation
- type: prompt
provider: claude
prompt: |
Suggest 3 improvements for this {language} code.
Consider readability, performance, and best practices.
Code:
{input}
Current understanding:
{explanation}
output_var: improvements
output: |
## Language: {language}
## Explanation
{explanation}
## Suggested Improvements
{improvements}</code></pre>
</details>
<h2 id="debugging-tips">Debugging Multi-Step Tools</h2>
<pre><code class="language-bash"># See what prompts are being built
cat test.txt | my-tool --dry-run
# Watch each step execute
cat test.txt | my-tool --verbose
# Test with mock provider first
cat test.txt | my-tool --provider mock</code></pre>
<h2 id="next-up">Next Up</h2>
<p>Ready to go deeper? Learn the full power of code steps:</p>
<ul>
<li><a href="/tutorials/code-steps">Code Steps Deep Dive</a> - Python superpowers in your tools</li>
<li><a href="/tutorials/advanced-workflows">Advanced Workflows</a> - Multi-provider, conditional logic, and more</li>
</ul>
""",
"headings": [
("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": "Python Superpowers",
"description": "Inject Python into your AI workflows for ultimate control",
"content": """
<p class="lead">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.</p>
<div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 my-6">
<p class="font-semibold text-indigo-800">What You'll Learn</p>
<ul class="mt-2 text-indigo-700">
<li>How to embed Python code in your tools</li>
<li>Accessing variables from previous steps</li>
<li>The cookbook of common operations</li>
<li>Error handling that doesn't break your tools</li>
</ul>
</div>
<h2 id="hello-code">Your First Code Step</h2>
<p>The simplest code step looks like this:</p>
<pre><code class="language-yaml">steps:
- type: code
code: |
result = input.upper()
output_var: result</code></pre>
<p>That's it. Whatever you assign to <code>result</code> becomes available as <code>{result}</code> in subsequent steps.</p>
<div class="bg-white border rounded-lg p-4 my-6">
<p class="font-medium text-gray-700 mb-2">What you have access to:</p>
<div class="grid grid-cols-3 gap-4 text-center">
<div class="bg-blue-50 rounded p-2">
<code class="text-sm">input</code>
<p class="text-xs text-gray-500 mt-1">The original stdin</p>
</div>
<div class="bg-green-50 rounded p-2">
<code class="text-sm">arg_name</code>
<p class="text-xs text-gray-500 mt-1">Any --flag values</p>
</div>
<div class="bg-purple-50 rounded p-2">
<code class="text-sm">prev_output</code>
<p class="text-xs text-gray-500 mt-1">Previous step outputs</p>
</div>
</div>
</div>
<h2 id="real-example">A Real Example: Smart Word Counter</h2>
<p>Let's build something useful—a word counter that also finds the most common words:</p>
<pre><code class="language-yaml">name: wordstats
version: "1.0.0"
description: Analyze text statistics
steps:
- type: code
code: |
import re
from collections import Counter
# Clean and split
words = re.findall(r'\\b\\w+\\b', input.lower())
# Calculate stats
word_count = len(words)
unique_count = len(set(words))
char_count = len(input)
# 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
output: |
Words: {word_count}
Unique: {unique_count}
Characters: {char_count}
Top 5 words:
{top_formatted}</code></pre>
<div class="bg-cyan-50 border-l-4 border-cyan-500 p-4 my-6">
<p class="font-semibold text-cyan-800">Pro Tip: Export Multiple Variables</p>
<p class="text-cyan-700">Notice <code>output_var: word_count, unique_count, char_count, top_formatted</code>—
you can export as many variables as you need. Just list them comma-separated.</p>
</div>
<h2 id="the-cookbook">The Code Step Cookbook</h2>
<p>Copy-paste these recipes for common tasks:</p>
<div class="space-y-6 my-6">
<div class="bg-white border rounded-lg overflow-hidden">
<div class="bg-gray-100 px-4 py-2 font-medium">Parse JSON from AI</div>
<pre class="p-4 m-0"><code class="language-yaml">- type: code
code: |
import json
try:
data = json.loads(ai_response)
result = json.dumps(data, indent=2)
except json.JSONDecodeError:
result = ai_response # Fallback to raw
output_var: result</code></pre>
</div>
<div class="bg-white border rounded-lg overflow-hidden">
<div class="bg-gray-100 px-4 py-2 font-medium">Extract with Regex</div>
<pre class="p-4 m-0"><code class="language-yaml">- 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</code></pre>
</div>
<div class="bg-white border rounded-lg overflow-hidden">
<div class="bg-gray-100 px-4 py-2 font-medium">Remove Empty Lines</div>
<pre class="p-4 m-0"><code class="language-yaml">- type: code
code: |
lines = [l for l in input.split('\\n') if l.strip()]
cleaned = '\\n'.join(lines)
output_var: cleaned</code></pre>
</div>
<div class="bg-white border rounded-lg overflow-hidden">
<div class="bg-gray-100 px-4 py-2 font-medium">Add Timestamps</div>
<pre class="p-4 m-0"><code class="language-yaml">- type: code
code: |
from datetime import datetime
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
result = f"[{timestamp}] {input}"
output_var: result</code></pre>
</div>
<div class="bg-white border rounded-lg overflow-hidden">
<div class="bg-gray-100 px-4 py-2 font-medium">Limit/Truncate Text</div>
<pre class="p-4 m-0"><code class="language-yaml">- 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</code></pre>
</div>
</div>
<h2 id="error-handling">Bulletproof Error Handling</h2>
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 my-6">
<p class="font-semibold text-amber-800">Warning: Crashes Kill Your Tool</p>
<p class="text-amber-700">An uncaught exception stops execution immediately. Always wrap risky code in try/except.</p>
</div>
<pre><code class="language-yaml">- 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</code></pre>
<h2 id="combining-with-ai">The AI + Code Power Combo</h2>
<p>The real magic happens when you alternate between AI and code:</p>
<pre><code class="language-yaml">name: structured-extract
version: "1.0.0"
description: Extract and validate structured data
steps:
# AI extracts data
- type: prompt
provider: claude
prompt: |
Extract all dates from this text.
Return as JSON array: ["YYYY-MM-DD", ...]
{input}
output_var: dates_json
# 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
prompt: |
Format these {count} dates in a human-readable way:
{result}
output_var: formatted
output: "{formatted}"</code></pre>
<h2 id="try-it">Try It: Build a CSV Analyzer</h2>
<div class="bg-green-50 border-l-4 border-green-500 p-4 my-6">
<p class="font-semibold text-green-800">Exercise</p>
<p class="text-green-700">Create a tool that:</p>
<ol class="mt-2 text-green-700 list-decimal list-inside">
<li>Takes CSV data as input</li>
<li>Uses Python to count rows and extract headers</li>
<li>Asks AI to describe what the data likely represents</li>
</ol>
</div>
<details class="my-4">
<summary class="cursor-pointer text-indigo-600 font-medium">See the solution</summary>
<pre class="mt-2"><code class="language-yaml">name: csv-describe
version: "1.0.0"
description: Understand CSV data at a glance
steps:
- type: code
code: |
import csv
from io import StringIO
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: |
This CSV has {row_count} rows with these columns:
{headers_str}
Sample data:
{sample_str}
What does this data likely represent?
What insights could we extract from it?
output_var: analysis
output: |
Columns: {headers_str}
Rows: {row_count}
{analysis}</code></pre>
</details>
<h2 id="what-you-can-import">What You Can Import</h2>
<p>The Python standard library is fully available:</p>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2 my-4 text-sm">
<div class="bg-gray-100 px-2 py-1 rounded"><code>json</code></div>
<div class="bg-gray-100 px-2 py-1 rounded"><code>re</code></div>
<div class="bg-gray-100 px-2 py-1 rounded"><code>csv</code></div>
<div class="bg-gray-100 px-2 py-1 rounded"><code>datetime</code></div>
<div class="bg-gray-100 px-2 py-1 rounded"><code>pathlib</code></div>
<div class="bg-gray-100 px-2 py-1 rounded"><code>collections</code></div>
<div class="bg-gray-100 px-2 py-1 rounded"><code>itertools</code></div>
<div class="bg-gray-100 px-2 py-1 rounded"><code>math</code></div>
</div>
<p>Third-party packages work too—just make sure they're installed in your environment.</p>
<h2 id="security">Security: The Rules</h2>
<div class="bg-red-50 border-l-4 border-red-500 p-4 my-6">
<p class="font-semibold text-red-800">Never Do This</p>
<ul class="text-red-700 mt-2">
<li><code>eval(input)</code> - Code injection waiting to happen</li>
<li><code>exec(user_data)</code> - Same problem</li>
<li><code>os.system(input)</code> - Shell injection</li>
</ul>
</div>
<p>Code steps run with your user permissions. Treat input as untrusted data—parse it, don't execute it.</p>
<h2 id="next-up">Level Up</h2>
<p>Ready for the advanced stuff?</p>
<ul>
<li><a href="/tutorials/advanced-workflows">Advanced Workflows</a> - Multi-provider, conditional logic, external tools</li>
<li><a href="/docs/parallel-orchestration">Parallel Orchestration</a> - Run multiple tools simultaneously</li>
</ul>
""",
"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": """
<p class="lead">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.</p>
<div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 my-6">
<p class="font-semibold text-indigo-800">What You'll Master</p>
<ul class="mt-2 text-indigo-700">
<li>Using different AI models for different tasks</li>
<li>Building tools that adapt to their input</li>
<li>Self-critiquing workflows that iterate to perfection</li>
<li>Calling external tools from within your CmdForge</li>
</ul>
</div>
<h2 id="right-model-right-job">Right Model, Right Job</h2>
<p>Different AI models have different strengths. A smart tool uses them strategically:</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6 text-sm">
<div class="bg-blue-50 border border-blue-200 rounded-lg p-3">
<p class="font-bold text-blue-800">Fast & Cheap</p>
<p class="text-blue-700">Haiku, Grok-mini</p>
<p class="text-xs text-blue-600 mt-1">Extraction, formatting, simple tasks</p>
</div>
<div class="bg-green-50 border border-green-200 rounded-lg p-3">
<p class="font-bold text-green-800">Balanced</p>
<p class="text-green-700">Sonnet, GPT-4o-mini</p>
<p class="text-xs text-green-600 mt-1">Analysis, writing, code review</p>
</div>
<div class="bg-purple-50 border border-purple-200 rounded-lg p-3">
<p class="font-bold text-purple-800">Maximum Power</p>
<p class="text-purple-700">Opus, GPT-4, DeepSeek</p>
<p class="text-xs text-purple-600 mt-1">Complex reasoning, synthesis</p>
</div>
</div>
<pre><code class="language-yaml">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}"</code></pre>
<div class="bg-cyan-50 border-l-4 border-cyan-500 p-4 my-6">
<p class="font-semibold text-cyan-800">Pro Tip: Cost Optimization</p>
<p class="text-cyan-700">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.</p>
</div>
<h2 id="adaptive-tools">Tools That Adapt to Their Input</h2>
<p>The best tools don't assume what they're getting. They figure it out:</p>
<pre><code class="language-yaml">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: |
This input is {format_type} format.
{instruction}
Input:
{input}
output_var: result
output: "[{format_type}] {result}"</code></pre>
<h2 id="self-improving">The Self-Improving Loop</h2>
<p>Want better quality? Make your tool critique itself:</p>
<pre><code class="language-yaml">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}"</code></pre>
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 my-6">
<p class="font-semibold text-amber-800">Warning: Know When to Stop</p>
<p class="text-amber-700">More iterations don't always mean better output. 2-3 passes is usually the sweet spot.
Beyond that, you're paying for diminishing returns.</p>
</div>
<h2 id="dynamic-prompts">Dynamic Prompt Building</h2>
<p>Let Python construct your prompts on the fly:</p>
<pre><code class="language-yaml">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: |
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 = 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
provider: claude
prompt: |
{instruction}
{input}
output_var: result
output: "{result}"</code></pre>
<h2 id="external-tools">Calling External Tools</h2>
<p>CmdForge can wrap any command-line tool:</p>
<pre><code class="language-yaml">name: lint-explain
version: "1.0.0"
description: Runs pylint and explains the results
steps:
# Run the linter
- type: code
code: |
import subprocess
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
# Explain in plain English
- type: prompt
provider: claude
prompt: |
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: |
## Lint Results
```
{lint_output}
```
## Explanation
{explanation}</code></pre>
<h2 id="try-it">Try It: Build a Research Assistant</h2>
<div class="bg-green-50 border-l-4 border-green-500 p-4 my-6">
<p class="font-semibold text-green-800">Boss Level Exercise</p>
<p class="text-green-700">Create a tool that:</p>
<ol class="mt-2 text-green-700 list-decimal list-inside">
<li>Detects whether input is a question or a topic</li>
<li>Uses a fast model to generate 3 research angles</li>
<li>Uses a powerful model to explore the best angle</li>
<li>Adds a code step to format with headers and timestamps</li>
</ol>
</div>
<details class="my-4">
<summary class="cursor-pointer text-indigo-600 font-medium">See the solution</summary>
<pre class="mt-2"><code class="language-yaml">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}"</code></pre>
</details>
<h2 id="performance">Performance Secrets</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-6">
<div class="bg-white border rounded-lg p-4">
<p class="font-bold text-gray-800 mb-2">Speed Tricks</p>
<ul class="text-sm text-gray-600 space-y-1">
<li>Use Haiku/Grok for extraction</li>
<li>Combine related tasks in one prompt</li>
<li>Skip AI when Python can do it</li>
</ul>
</div>
<div class="bg-white border rounded-lg p-4">
<p class="font-bold text-gray-800 mb-2">Cost Tricks</p>
<ul class="text-sm text-gray-600 space-y-1">
<li>Powerful models only for synthesis</li>
<li>Truncate long inputs with code first</li>
<li>Cache repeated operations</li>
</ul>
</div>
</div>
<h2 id="whats-next">What's Next?</h2>
<p>You've learned the advanced patterns. Now go parallel:</p>
<ul>
<li><a href="/docs/parallel-orchestration">Parallel Orchestration</a> - Run multiple tools simultaneously</li>
<li><a href="/docs/publishing">Publishing Tools</a> - Share your creations with the world</li>
</ul>
""",
"headings": [
("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?"),
],
},
"visual-builder": {
"title": "The Visual Builder",
"description": "Build tools visually with the CmdForge desktop GUI",
"content": """
<p class="lead">Not everyone wants to write YAML by hand. CmdForge includes a modern desktop
application that lets you create, edit, and manage tools with a visual interface—no text editor required.</p>
<div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 my-6">
<p class="font-semibold text-indigo-800">What You'll Learn</p>
<ul class="mt-2 text-indigo-700">
<li>How to launch and navigate the desktop application</li>
<li>Creating and editing tools with the visual builder</li>
<li>Browsing and installing tools from the registry</li>
<li>Managing AI providers</li>
<li>Testing tools before deploying them</li>
</ul>
</div>
<h2 id="launching">Launching the Application</h2>
<p>Start the desktop application with a single command:</p>
<pre><code class="language-bash">cmdforge</code></pre>
<p>The application opens with a clean, modern interface featuring a sidebar for navigation and a main content area:</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-6">
<div class="bg-white border-2 border-indigo-200 rounded-lg p-4">
<p class="font-bold text-indigo-800">Sidebar Navigation</p>
<ul class="text-sm text-indigo-700 mt-2">
<li><strong>My Tools</strong> - View and manage your tools</li>
<li><strong>Registry</strong> - Browse and install community tools</li>
<li><strong>Providers</strong> - Configure AI backends</li>
<li><strong>Profiles</strong> - Manage account connections</li>
</ul>
</div>
<div class="bg-white border-2 border-green-200 rounded-lg p-4">
<p class="font-bold text-green-800">Features</p>
<ul class="text-sm text-green-700 mt-2">
<li>Light and dark themes</li>
<li>Keyboard shortcuts for power users</li>
<li>One-click registry connection</li>
<li>Real-time status syncing</li>
</ul>
</div>
</div>
<div class="bg-cyan-50 border-l-4 border-cyan-500 p-4 my-6">
<p class="font-semibold text-cyan-800">Pro Tip: Theme Switching</p>
<p class="text-cyan-700">Use the <strong>View</strong> menu to switch between light and dark themes.
Your preference is saved automatically.</p>
</div>
<h2 id="my-tools">The My Tools Page</h2>
<p>The My Tools page shows all your installed tools organized by category in a tree view:</p>
<ul>
<li><strong>Category grouping</strong> - Tools are organized under categories like Text Processing, Developer, Data, etc.</li>
<li><strong>Tool details panel</strong> - Select a tool to see its description, arguments, and processing steps</li>
<li><strong>Publish status</strong> - Icons indicate whether a tool is published, pending review, or local-only</li>
</ul>
<p>From this page you can:</p>
<div class="grid grid-cols-1 md:grid-cols-4 gap-3 my-6 text-sm">
<div class="bg-blue-50 border border-blue-200 rounded-lg p-3 text-center">
<p class="font-bold text-blue-800">Create</p>
<p class="text-blue-700">New tool</p>
</div>
<div class="bg-green-50 border border-green-200 rounded-lg p-3 text-center">
<p class="font-bold text-green-800">Edit</p>
<p class="text-green-700">Existing tool</p>
</div>
<div class="bg-purple-50 border border-purple-200 rounded-lg p-3 text-center">
<p class="font-bold text-purple-800">Publish</p>
<p class="text-purple-700">To registry</p>
</div>
<div class="bg-red-50 border border-red-200 rounded-lg p-3 text-center">
<p class="font-bold text-red-800">Delete</p>
<p class="text-red-700">Remove tool</p>
</div>
</div>
<h2 id="creating-tools">Creating Tools with the Visual Builder</h2>
<p>Click <strong>New Tool</strong> to open the tool builder. It has a form-based interface split into sections:</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-6">
<div class="bg-white border-2 border-green-200 rounded-lg p-4">
<p class="font-bold text-green-800">Basic Information</p>
<ul class="text-sm text-green-700 mt-2">
<li><strong>Name</strong> - Unique identifier (lowercase, hyphens allowed)</li>
<li><strong>Description</strong> - Brief summary for listings</li>
<li><strong>Category</strong> - Select or create a category</li>
</ul>
</div>
<div class="bg-white border-2 border-blue-200 rounded-lg p-4">
<p class="font-bold text-blue-800">Arguments</p>
<ul class="text-sm text-blue-700 mt-2">
<li>Add custom flags (--format, --max, etc.)</li>
<li>Set default values and help text</li>
<li>Arguments become variables in your prompts</li>
</ul>
</div>
</div>
<div class="bg-white border-2 border-purple-200 rounded-lg p-4 my-6">
<p class="font-bold text-purple-800">Processing Steps</p>
<p class="text-sm text-purple-700 mt-2">Build your tool's logic by adding steps:</p>
<ul class="text-sm text-purple-700 mt-2">
<li><strong>Prompt Steps</strong> - Send input to an AI provider with a template</li>
<li><strong>Code Steps</strong> - Process data with Python code</li>
<li>Reorder steps by dragging or using move buttons</li>
<li>Each step can pass its output to the next via variables</li>
</ul>
</div>
<h3>Step-by-Step: Creating a Summarizer</h3>
<ol class="space-y-3">
<li><strong>Launch:</strong> Run <code>cmdforge</code> to open the application</li>
<li><strong>Navigate:</strong> Click <strong>My Tools</strong> in the sidebar</li>
<li><strong>Create:</strong> Click the <strong>New Tool</strong> button</li>
<li><strong>Basic Info:</strong> Enter name <code>summarize</code> and a description</li>
<li><strong>Add Argument:</strong> Click <strong>Add Argument</strong>, set flag to <code>--length</code> with default <code>100</code></li>
<li><strong>Add Step:</strong> Click <strong>Add Step</strong> and choose <strong>Prompt Step</strong></li>
<li><strong>Configure Step:</strong> Select your AI provider and enter the prompt template</li>
<li><strong>Save:</strong> Click <strong>Save</strong> to create your tool</li>
</ol>
<h2 id="registry">Browsing the Registry</h2>
<p>The <strong>Registry</strong> page lets you discover and install tools created by the community:</p>
<ul>
<li><strong>Search</strong> - Find tools by name or keyword</li>
<li><strong>Filter by category</strong> - Browse Text Processing, Developer, Data tools, etc.</li>
<li><strong>Sort</strong> - Order by downloads, newest, or alphabetically</li>
<li><strong>Tool details</strong> - View description, author, version history, and download count</li>
<li><strong>One-click install</strong> - Install any tool to your local machine</li>
</ul>
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 my-6">
<p class="font-semibold text-amber-800">Version Selection</p>
<p class="text-amber-700">When installing a tool, you can choose a specific version or get the latest.
This is useful when you need a particular version for compatibility.</p>
</div>
<h2 id="managing-providers">Managing Providers</h2>
<p>The <strong>Providers</strong> page shows all configured AI backends in a table view:</p>
<table class="w-full my-4">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left">Column</th>
<th class="px-4 py-2 text-left">Description</th>
</tr>
</thead>
<tbody>
<tr class="border-b">
<td class="px-4 py-2"><strong>Name</strong></td>
<td class="px-4 py-2">Provider identifier used in tool configs</td>
</tr>
<tr class="border-b">
<td class="px-4 py-2"><strong>Command</strong></td>
<td class="px-4 py-2">The CLI command that's executed</td>
</tr>
<tr>
<td class="px-4 py-2"><strong>Description</strong></td>
<td class="px-4 py-2">What this provider does</td>
</tr>
</tbody>
</table>
<p>From this page you can:</p>
<ul>
<li><strong>Add Provider</strong> - Create a new provider with name, command, and description</li>
<li><strong>Edit</strong> - Modify an existing provider's configuration</li>
<li><strong>Delete</strong> - Remove a provider you no longer need</li>
<li><strong>Test</strong> - Send a test prompt to verify the provider works</li>
</ul>
<h2 id="testing">Testing Tools</h2>
<p>Test your tools before deploying them using the step-by-step test feature:</p>
<ol>
<li>In the <strong>Tool Builder</strong>, click <strong>Test</strong> on any step</li>
<li>Enter sample input text</li>
<li>Set any argument values</li>
<li>Run the test to see the output</li>
</ol>
<p>This lets you verify prompts and variable substitution. For quick tests without API calls, configure a
<code>mock</code> provider that echoes input back.</p>
<h2 id="keyboard-shortcuts">Keyboard Shortcuts</h2>
<p>Power users can navigate quickly with these shortcuts:</p>
<table class="w-full my-4">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left">Shortcut</th>
<th class="px-4 py-2 text-left">Action</th>
</tr>
</thead>
<tbody>
<tr class="border-b">
<td class="px-4 py-2"><code>Ctrl+1</code> to <code>Ctrl+4</code></td>
<td class="px-4 py-2">Switch to page (Tools, Registry, Providers, Profiles)</td>
</tr>
<tr class="border-b">
<td class="px-4 py-2"><code>Ctrl+N</code></td>
<td class="px-4 py-2">Create new tool</td>
</tr>
<tr class="border-b">
<td class="px-4 py-2"><code>Ctrl+S</code></td>
<td class="px-4 py-2">Save current tool</td>
</tr>
<tr class="border-b">
<td class="px-4 py-2"><code>Escape</code></td>
<td class="px-4 py-2">Cancel / go back</td>
</tr>
<tr>
<td class="px-4 py-2"><code>Ctrl+Q</code></td>
<td class="px-4 py-2">Quit the application</td>
</tr>
</tbody>
</table>
<h2 id="cli-vs-gui">CLI vs Visual Builder</h2>
<p>When should you use each?</p>
<table class="w-full my-4">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left">Use the Visual Builder when...</th>
<th class="px-4 py-2 text-left">Use CLI/YAML when...</th>
</tr>
</thead>
<tbody>
<tr class="border-b">
<td class="px-4 py-2">You're new to CmdForge</td>
<td class="px-4 py-2">You're comfortable with YAML</td>
</tr>
<tr class="border-b">
<td class="px-4 py-2">Building your first few tools</td>
<td class="px-4 py-2">Making quick edits to configs</td>
</tr>
<tr class="border-b">
<td class="px-4 py-2">Browsing the registry</td>
<td class="px-4 py-2">Scripting tool installation</td>
</tr>
<tr class="border-b">
<td class="px-4 py-2">Managing providers visually</td>
<td class="px-4 py-2">Copying tools between machines</td>
</tr>
<tr>
<td class="px-4 py-2">Publishing tools to the registry</td>
<td class="px-4 py-2">CI/CD integration</td>
</tr>
</tbody>
</table>
<h2 id="next-up">Next Up</h2>
<p>Now that you know how to use the Visual Builder:</p>
<ul>
<li><a href="/tutorials/yaml-config">YAML Configuration</a> - Deep dive into the config format</li>
<li><a href="/tutorials/arguments">Adding Arguments</a> - Custom flags and options</li>
<li><a href="/docs/providers">Providers</a> - Configure AI backends</li>
<li><a href="/docs/publishing">Publishing Tools</a> - Share your tools with the community</li>
</ul>
""",
"headings": [
("launching", "Launching the Application"),
("my-tools", "The My Tools Page"),
("creating-tools", "Creating Tools with the Visual Builder"),
("registry", "Browsing the Registry"),
("managing-providers", "Managing Providers"),
("testing", "Testing Tools"),
("keyboard-shortcuts", "Keyboard Shortcuts"),
("cli-vs-gui", "CLI vs Visual Builder"),
("next-up", "Next Up"),
],
},
}
def get_doc(path: str) -> dict:
"""Get documentation content by path."""
# Normalize path
path = path.strip("/").replace("docs/", "") or "getting-started"
return DOCS.get(path, None)
def get_toc():
"""Get table of contents structure."""
from types import SimpleNamespace
return [
SimpleNamespace(slug="getting-started", title="Getting Started", children=[
SimpleNamespace(slug="installation", title="Installation"),
SimpleNamespace(slug="first-tool", title="Your First Tool"),
SimpleNamespace(slug="visual-builder", title="Visual Builder"),
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="providers", title="Providers", children=[]),
SimpleNamespace(slug="publishing", title="Publishing", children=[]),
SimpleNamespace(slug="advanced-workflows", title="Advanced Workflows", children=[
SimpleNamespace(slug="parallel-orchestration", title="Parallel Orchestration"),
]),
]