2447 lines
83 KiB
Python
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 & 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"),
|
|
]),
|
|
]
|