1276 lines
41 KiB
Python
1276 lines
41 KiB
Python
"""Documentation content for SmartTools 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 SmartTools and create your first AI-powered CLI tool",
|
|
"content": """
|
|
<p class="lead">SmartTools 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-smarttools">What is SmartTools?</h2>
|
|
<p>SmartTools 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 SmartTools
|
|
pip install smarttools
|
|
|
|
# Create your first tool interactively
|
|
smarttools create
|
|
|
|
# Or install a tool from the registry
|
|
smarttools registry install official/summarize
|
|
|
|
# Use it!
|
|
cat article.txt | summarize</code></pre>
|
|
|
|
<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/first-tool">Your First Tool</a> - Step-by-step 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>
|
|
""",
|
|
"headings": [
|
|
("what-is-smarttools", "What is SmartTools?"),
|
|
("quick-start", "Quick Start"),
|
|
("how-it-works", "How It Works"),
|
|
("next-steps", "Next Steps"),
|
|
],
|
|
},
|
|
|
|
"installation": {
|
|
"title": "Installation",
|
|
"description": "How to install SmartTools on your system",
|
|
"parent": "getting-started",
|
|
"content": """
|
|
<p class="lead">SmartTools 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 SmartTools:</p>
|
|
<pre><code class="language-bash">pip install smarttools</code></pre>
|
|
|
|
<p>Or with pipx for isolated installation:</p>
|
|
<pre><code class="language-bash">pipx install smarttools</code></pre>
|
|
|
|
<h2 id="verify">Verify Installation</h2>
|
|
<pre><code class="language-bash">smarttools --version
|
|
smarttools --help</code></pre>
|
|
|
|
<h2 id="configure-provider">Configure a Provider</h2>
|
|
<p>SmartTools 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
|
|
smarttools config</code></pre>
|
|
|
|
<h2 id="wrapper-scripts">Wrapper Scripts Location</h2>
|
|
<p>SmartTools 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 SmartTools:</p>
|
|
<pre><code class="language-bash">git clone https://gitea.brrd.tech/rob/SmartTools.git
|
|
cd SmartTools
|
|
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 SmartTools 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">smarttools create</code></pre>
|
|
|
|
<p>Or create the file manually at <code>~/.smarttools/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 SmartTools community",
|
|
"content": """
|
|
<p class="lead">Share your tools with the community by publishing to the SmartTools 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="get-token">Get an API Token</h2>
|
|
<ol>
|
|
<li>Go to your <a href="/dashboard/tokens">Dashboard → Tokens</a></li>
|
|
<li>Click "Create New Token"</li>
|
|
<li>Copy the token (shown only once!)</li>
|
|
</ol>
|
|
|
|
<h2 id="publish">Publish Your Tool</h2>
|
|
<pre><code class="language-bash"># Navigate to your tool directory
|
|
cd ~/.smarttools/my-tool/
|
|
|
|
# First time: enter your token when prompted
|
|
smarttools registry publish
|
|
|
|
# Dry run to validate without publishing
|
|
smarttools registry publish --dry-run</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>smarttools registry publish</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>
|
|
</ul>
|
|
""",
|
|
"headings": [
|
|
("before-publishing", "Before Publishing"),
|
|
("create-account", "Create an Account"),
|
|
("get-token", "Get an API Token"),
|
|
("publish", "Publish Your Tool"),
|
|
("versioning", "Versioning"),
|
|
("best-practices", "Best Practices"),
|
|
],
|
|
},
|
|
|
|
"providers": {
|
|
"title": "AI Providers",
|
|
"description": "Configure different AI providers for your tools",
|
|
"content": """
|
|
<p class="lead">SmartTools works with any AI provider that has a CLI interface. Configure providers in
|
|
<code>~/.smarttools/providers.yaml</code>.</p>
|
|
|
|
<h2 id="provider-config">Provider Configuration</h2>
|
|
<p>Create or edit <code>~/.smarttools/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 SmartTools concurrently for faster workflows",
|
|
"content": """
|
|
<p class="lead">SmartTools 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 SmartTools 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 SmartTools 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 SmartTools</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": "Understanding YAML Config",
|
|
"description": "Learn the structure of SmartTools configuration files",
|
|
"content": """
|
|
<p class="lead">Every SmartTool is defined by a YAML configuration file. This guide covers the complete
|
|
structure and all available options.</p>
|
|
|
|
<h2 id="file-location">File Location</h2>
|
|
<p>Tool configs are stored in <code>~/.smarttools/<tool-name>/config.yaml</code>.</p>
|
|
|
|
<h2 id="complete-structure">Complete Structure</h2>
|
|
<pre><code class="language-yaml"># Required fields
|
|
name: my-tool # Tool name (lowercase, hyphens)
|
|
version: "1.0.0" # Semver version string
|
|
|
|
# Recommended fields
|
|
description: "What this tool does"
|
|
category: text-processing # For registry organization
|
|
tags: # Searchable tags
|
|
- text
|
|
- formatting
|
|
|
|
# Optional metadata
|
|
author: your-name
|
|
license: MIT
|
|
homepage: https://github.com/you/my-tool
|
|
|
|
# Arguments (custom CLI flags)
|
|
arguments:
|
|
- flag: --format
|
|
variable: format
|
|
default: "markdown"
|
|
description: Output format
|
|
|
|
# Processing steps
|
|
steps:
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Process this: {input}
|
|
output_var: result
|
|
|
|
# Final output template
|
|
output: "{result}"</code></pre>
|
|
|
|
<h2 id="required-fields">Required Fields</h2>
|
|
|
|
<h3>name</h3>
|
|
<p>The tool's identifier. Must be lowercase with hyphens only:</p>
|
|
<pre><code class="language-yaml">name: my-cool-tool # Good
|
|
name: MyCoolTool # Bad - no uppercase
|
|
name: my_cool_tool # Bad - no underscores</code></pre>
|
|
|
|
<h3>version</h3>
|
|
<p>Semantic version string. Always quote it to prevent YAML parsing issues:</p>
|
|
<pre><code class="language-yaml">version: "1.0.0" # Good
|
|
version: 1.0 # Bad - YAML parses as float</code></pre>
|
|
|
|
<h2 id="variable-substitution">Variable Substitution</h2>
|
|
<p>Use <code>{variable}</code> syntax in prompts and output:</p>
|
|
<ul>
|
|
<li><code>{input}</code> - Content piped to the tool</li>
|
|
<li><code>{variable_name}</code> - From arguments or previous steps</li>
|
|
</ul>
|
|
|
|
<p>To include literal braces, double them:</p>
|
|
<pre><code class="language-yaml">prompt: |
|
|
Format as JSON: {{\"key\": \"value\"}}
|
|
Input: {input}</code></pre>
|
|
|
|
<h2 id="categories">Categories</h2>
|
|
<p>Standard categories for the registry:</p>
|
|
<ul>
|
|
<li><code>text-processing</code> - Summarize, translate, format</li>
|
|
<li><code>code-analysis</code> - Review, explain, generate</li>
|
|
<li><code>data-extraction</code> - Parse, extract, convert</li>
|
|
<li><code>content-creation</code> - Write, expand, draft</li>
|
|
<li><code>productivity</code> - Automate, organize</li>
|
|
<li><code>education</code> - Explain, teach, simplify</li>
|
|
</ul>
|
|
|
|
<h2 id="validation">Validation</h2>
|
|
<p>Test your config without running:</p>
|
|
<pre><code class="language-bash"># Validate syntax
|
|
smarttools test my-tool --dry-run
|
|
|
|
# Check for common issues
|
|
smarttools registry publish --dry-run</code></pre>
|
|
""",
|
|
"headings": [
|
|
("file-location", "File Location"),
|
|
("complete-structure", "Complete Structure"),
|
|
("required-fields", "Required Fields"),
|
|
("variable-substitution", "Variable Substitution"),
|
|
("categories", "Categories"),
|
|
("validation", "Validation"),
|
|
],
|
|
},
|
|
|
|
"arguments": {
|
|
"title": "Custom Arguments",
|
|
"description": "Add flags and options to make your tools flexible",
|
|
"content": """
|
|
<p class="lead">Arguments let users customize tool behavior with CLI flags like <code>--format json</code>
|
|
or <code>--verbose</code>.</p>
|
|
|
|
<h2 id="basic-syntax">Basic Syntax</h2>
|
|
<pre><code class="language-yaml">arguments:
|
|
- flag: --format # The CLI flag
|
|
variable: format # Variable name in templates
|
|
default: "text" # Default value if not provided
|
|
description: "Output format (text, json, markdown)"</code></pre>
|
|
|
|
<h2 id="using-arguments">Using Arguments</h2>
|
|
<p>Reference arguments in prompts using <code>{variable_name}</code>:</p>
|
|
<pre><code class="language-yaml">arguments:
|
|
- flag: --tone
|
|
variable: tone
|
|
default: "professional"
|
|
|
|
steps:
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Rewrite this text with a {tone} tone:
|
|
|
|
{input}
|
|
output_var: result</code></pre>
|
|
|
|
<p>Users can then run:</p>
|
|
<pre><code class="language-bash">echo "Hey, fix this bug ASAP!" | tone-shift --tone friendly</code></pre>
|
|
|
|
<h2 id="multiple-arguments">Multiple Arguments</h2>
|
|
<pre><code class="language-yaml">arguments:
|
|
- flag: --lang
|
|
variable: language
|
|
default: "English"
|
|
description: "Target language"
|
|
|
|
- flag: --formality
|
|
variable: formality
|
|
default: "neutral"
|
|
description: "Formality level (casual, neutral, formal)"
|
|
|
|
- flag: --max-length
|
|
variable: max_length
|
|
default: "500"
|
|
description: "Maximum output length in words"</code></pre>
|
|
|
|
<h2 id="argument-types">Argument Patterns</h2>
|
|
|
|
<h3>Choice Arguments</h3>
|
|
<p>Document valid choices in the description:</p>
|
|
<pre><code class="language-yaml">- flag: --style
|
|
variable: style
|
|
default: "concise"
|
|
description: "Writing style: concise, detailed, or academic"</code></pre>
|
|
|
|
<h3>Numeric Arguments</h3>
|
|
<p>Always quote defaults to avoid YAML issues:</p>
|
|
<pre><code class="language-yaml">- flag: --max-tokens
|
|
variable: max_tokens
|
|
default: "1000" # Quoted string, not integer</code></pre>
|
|
|
|
<h3>Boolean-like Arguments</h3>
|
|
<p>Use string values for conditional prompts:</p>
|
|
<pre><code class="language-yaml">- flag: --verbose
|
|
variable: verbose
|
|
default: "no"
|
|
description: "Include detailed explanations (yes/no)"</code></pre>
|
|
|
|
<h2 id="in-prompts">Using in Prompts</h2>
|
|
<p>Combine multiple arguments in your prompt template:</p>
|
|
<pre><code class="language-yaml">steps:
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Translate the following text to {language}.
|
|
Use a {formality} register.
|
|
Keep the response under {max_length} words.
|
|
|
|
Text to translate:
|
|
{input}
|
|
output_var: translation</code></pre>
|
|
|
|
<h2 id="best-practices">Best Practices</h2>
|
|
<ul>
|
|
<li><strong>Use descriptive variable names</strong> - <code>target_language</code> not <code>tl</code></li>
|
|
<li><strong>Provide sensible defaults</strong> - Tools should work without any flags</li>
|
|
<li><strong>Document choices</strong> - List valid options in the description</li>
|
|
<li><strong>Keep flags short</strong> - Use <code>--lang</code> not <code>--target-language</code></li>
|
|
</ul>
|
|
""",
|
|
"headings": [
|
|
("basic-syntax", "Basic Syntax"),
|
|
("using-arguments", "Using Arguments"),
|
|
("multiple-arguments", "Multiple Arguments"),
|
|
("argument-types", "Argument Patterns"),
|
|
("in-prompts", "Using in Prompts"),
|
|
("best-practices", "Best Practices"),
|
|
],
|
|
},
|
|
|
|
"multi-step": {
|
|
"title": "Multi-Step Workflows",
|
|
"description": "Chain prompts and code steps together",
|
|
"content": """
|
|
<p class="lead">Complex tools can chain multiple steps together. Each step's output becomes available
|
|
to subsequent steps.</p>
|
|
|
|
<h2 id="step-flow">How Steps Flow</h2>
|
|
<pre><code class="language-yaml">steps:
|
|
# Step 1: Extract key points
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: "Extract 5 key points from: {input}"
|
|
output_var: key_points
|
|
|
|
# Step 2: Use step 1's output
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Create a summary from these points:
|
|
{key_points}
|
|
output_var: summary
|
|
|
|
output: "{summary}"</code></pre>
|
|
|
|
<p>Variables flow through the pipeline:</p>
|
|
<ul>
|
|
<li><code>{input}</code> → available in all steps</li>
|
|
<li><code>{key_points}</code> → available after step 1</li>
|
|
<li><code>{summary}</code> → available after step 2</li>
|
|
</ul>
|
|
|
|
<h2 id="mixed-steps">Mixing Prompt and Code Steps</h2>
|
|
<p>Combine AI calls with Python processing:</p>
|
|
<pre><code class="language-yaml">steps:
|
|
# Step 1: AI extracts data
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Extract all email addresses from this text as a comma-separated list:
|
|
{input}
|
|
output_var: emails_raw
|
|
|
|
# Step 2: Python cleans the data
|
|
- type: code
|
|
code: |
|
|
emails = [e.strip() for e in emails_raw.split(',')]
|
|
emails = [e for e in emails if '@' in e]
|
|
email_count = len(emails)
|
|
cleaned_emails = '\\n'.join(sorted(set(emails)))
|
|
output_var: cleaned_emails, email_count
|
|
|
|
# Step 3: AI formats output
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Format these {email_count} emails as a nice list:
|
|
{cleaned_emails}
|
|
output_var: formatted
|
|
|
|
output: "{formatted}"</code></pre>
|
|
|
|
<h2 id="error-handling">Step Dependencies</h2>
|
|
<p>If any step fails, execution stops. Design steps to handle edge cases:</p>
|
|
<pre><code class="language-yaml">steps:
|
|
- type: code
|
|
code: |
|
|
# Handle empty input gracefully
|
|
if not input.strip():
|
|
result = "No input provided"
|
|
skip_ai = "yes"
|
|
else:
|
|
result = input
|
|
skip_ai = "no"
|
|
output_var: result, skip_ai
|
|
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
{result}
|
|
# AI prompt only runs if skip_ai is "no"
|
|
output_var: ai_response</code></pre>
|
|
|
|
<h2 id="common-patterns">Common Patterns</h2>
|
|
|
|
<h3>Extract → Transform → Format</h3>
|
|
<pre><code class="language-yaml">steps:
|
|
- type: prompt # Extract structured data
|
|
- type: code # Transform/filter
|
|
- type: prompt # Format for output</code></pre>
|
|
|
|
<h3>Analyze → Synthesize</h3>
|
|
<pre><code class="language-yaml">steps:
|
|
- type: prompt # Break down into parts
|
|
- type: prompt # Combine insights</code></pre>
|
|
|
|
<h3>Validate → Process</h3>
|
|
<pre><code class="language-yaml">steps:
|
|
- type: code # Validate input format
|
|
- type: prompt # Process if valid</code></pre>
|
|
|
|
<h2 id="debugging">Debugging Multi-Step Tools</h2>
|
|
<pre><code class="language-bash"># Show prompts without running
|
|
cat test.txt | my-tool --dry-run
|
|
|
|
# See verbose output
|
|
cat test.txt | my-tool --verbose</code></pre>
|
|
""",
|
|
"headings": [
|
|
("step-flow", "How Steps Flow"),
|
|
("mixed-steps", "Mixing Prompt and Code Steps"),
|
|
("error-handling", "Step Dependencies"),
|
|
("common-patterns", "Common Patterns"),
|
|
("debugging", "Debugging Multi-Step Tools"),
|
|
],
|
|
},
|
|
|
|
"code-steps": {
|
|
"title": "Code Steps",
|
|
"description": "Add Python code processing between AI calls",
|
|
"content": """
|
|
<p class="lead">Code steps let you run Python code to process data, validate input, or transform
|
|
AI outputs between prompts.</p>
|
|
|
|
<h2 id="basic-syntax">Basic Syntax</h2>
|
|
<pre><code class="language-yaml">steps:
|
|
- type: code
|
|
code: |
|
|
# Python code here
|
|
result = input.upper()
|
|
output_var: result</code></pre>
|
|
|
|
<h2 id="available-variables">Available Variables</h2>
|
|
<p>Code steps have access to:</p>
|
|
<ul>
|
|
<li><code>input</code> - The original input text</li>
|
|
<li>All argument variables</li>
|
|
<li>Output variables from previous steps</li>
|
|
</ul>
|
|
|
|
<pre><code class="language-yaml">arguments:
|
|
- flag: --max
|
|
variable: max_items
|
|
default: "10"
|
|
|
|
steps:
|
|
- type: prompt
|
|
prompt: "List items from: {input}"
|
|
output_var: items_raw
|
|
|
|
- type: code
|
|
code: |
|
|
# Access argument and previous step output
|
|
items = items_raw.strip().split('\\n')
|
|
limited = items[:int(max_items)]
|
|
result = '\\n'.join(limited)
|
|
output_var: result</code></pre>
|
|
|
|
<h2 id="multiple-outputs">Multiple Output Variables</h2>
|
|
<p>Return multiple values with comma-separated output_var:</p>
|
|
<pre><code class="language-yaml">- type: code
|
|
code: |
|
|
lines = input.strip().split('\\n')
|
|
line_count = len(lines)
|
|
word_count = len(input.split())
|
|
char_count = len(input)
|
|
output_var: line_count, word_count, char_count</code></pre>
|
|
|
|
<h2 id="common-operations">Common Operations</h2>
|
|
|
|
<h3>Text Processing</h3>
|
|
<pre><code class="language-yaml">- type: code
|
|
code: |
|
|
# Remove empty lines
|
|
lines = [l for l in input.split('\\n') if l.strip()]
|
|
cleaned = '\\n'.join(lines)
|
|
output_var: cleaned</code></pre>
|
|
|
|
<h3>JSON Parsing</h3>
|
|
<pre><code class="language-yaml">- type: code
|
|
code: |
|
|
import json
|
|
data = json.loads(ai_response)
|
|
formatted = json.dumps(data, indent=2)
|
|
output_var: formatted</code></pre>
|
|
|
|
<h3>Data Validation</h3>
|
|
<pre><code class="language-yaml">- type: code
|
|
code: |
|
|
import re
|
|
emails = re.findall(r'[\\w.-]+@[\\w.-]+', input)
|
|
valid_emails = '\\n'.join(emails) if emails else "No emails found"
|
|
output_var: valid_emails</code></pre>
|
|
|
|
<h3>File Operations</h3>
|
|
<pre><code class="language-yaml">- type: code
|
|
code: |
|
|
from pathlib import Path
|
|
# Write to temp file
|
|
output_path = Path('/tmp/output.txt')
|
|
output_path.write_text(processed_text)
|
|
result = f"Saved to {output_path}"
|
|
output_var: result</code></pre>
|
|
|
|
<h2 id="using-imports">Using Imports</h2>
|
|
<p>Standard library imports work in code steps:</p>
|
|
<pre><code class="language-yaml">- type: code
|
|
code: |
|
|
import json
|
|
import re
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
timestamp = datetime.now().isoformat()
|
|
result = f"Processed at {timestamp}"
|
|
output_var: result</code></pre>
|
|
|
|
<h2 id="error-handling">Error Handling</h2>
|
|
<p>Handle exceptions to prevent tool failures:</p>
|
|
<pre><code class="language-yaml">- type: code
|
|
code: |
|
|
import json
|
|
try:
|
|
data = json.loads(ai_response)
|
|
result = data.get('summary', 'No summary found')
|
|
except json.JSONDecodeError:
|
|
result = ai_response # Fall back to raw response
|
|
output_var: result</code></pre>
|
|
|
|
<h2 id="security">Security Notes</h2>
|
|
<ul>
|
|
<li>Code runs with your user permissions</li>
|
|
<li>Don't use <code>eval()</code> on untrusted input</li>
|
|
<li>Be careful with file operations</li>
|
|
<li>Third-party packages must be installed separately</li>
|
|
</ul>
|
|
""",
|
|
"headings": [
|
|
("basic-syntax", "Basic Syntax"),
|
|
("available-variables", "Available Variables"),
|
|
("multiple-outputs", "Multiple Output Variables"),
|
|
("common-operations", "Common Operations"),
|
|
("using-imports", "Using Imports"),
|
|
("error-handling", "Error Handling"),
|
|
("security", "Security Notes"),
|
|
],
|
|
},
|
|
|
|
"advanced-workflows": {
|
|
"title": "Advanced Workflows",
|
|
"description": "Complex multi-provider and advanced tool patterns",
|
|
"content": """
|
|
<p class="lead">Take your tools to the next level with advanced patterns like multi-provider
|
|
workflows, dynamic prompts, and complex data pipelines.</p>
|
|
|
|
<h2 id="multi-provider">Multi-Provider Workflows</h2>
|
|
<p>Use different AI providers for different tasks:</p>
|
|
<pre><code class="language-yaml">steps:
|
|
# Fast model for extraction
|
|
- type: prompt
|
|
provider: opencode-grok
|
|
prompt: "Extract key facts from: {input}"
|
|
output_var: facts
|
|
|
|
# Powerful model for synthesis
|
|
- type: prompt
|
|
provider: claude-opus
|
|
prompt: |
|
|
Create a comprehensive analysis from these facts:
|
|
{facts}
|
|
output_var: analysis</code></pre>
|
|
|
|
<h2 id="conditional-logic">Conditional Logic with Code</h2>
|
|
<p>Use code steps to implement branching:</p>
|
|
<pre><code class="language-yaml">steps:
|
|
# Analyze input type
|
|
- type: code
|
|
code: |
|
|
if input.strip().startswith('{'):
|
|
input_type = "json"
|
|
processed = input
|
|
elif ',' in input and '\\n' in input:
|
|
input_type = "csv"
|
|
processed = input
|
|
else:
|
|
input_type = "text"
|
|
processed = input
|
|
output_var: input_type, processed
|
|
|
|
# Different prompt based on type
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
This is {input_type} data. Analyze it appropriately:
|
|
{processed}
|
|
output_var: result</code></pre>
|
|
|
|
<h2 id="iterative-refinement">Iterative Refinement</h2>
|
|
<p>Multiple passes for quality improvement:</p>
|
|
<pre><code class="language-yaml">steps:
|
|
# First draft
|
|
- type: prompt
|
|
provider: opencode-deepseek
|
|
prompt: "Write a summary of: {input}"
|
|
output_var: draft
|
|
|
|
# Critique
|
|
- type: prompt
|
|
provider: claude-haiku
|
|
prompt: |
|
|
Review this summary for accuracy and clarity.
|
|
List specific improvements needed:
|
|
{draft}
|
|
output_var: critique
|
|
|
|
# Final version
|
|
- type: prompt
|
|
provider: claude-sonnet
|
|
prompt: |
|
|
Improve this summary based on the feedback:
|
|
|
|
Original: {draft}
|
|
|
|
Feedback: {critique}
|
|
output_var: final</code></pre>
|
|
|
|
<h2 id="data-pipelines">Data Processing Pipelines</h2>
|
|
<pre><code class="language-yaml">name: csv-analyzer
|
|
steps:
|
|
# Parse CSV
|
|
- type: code
|
|
code: |
|
|
import csv
|
|
from io import StringIO
|
|
reader = csv.DictReader(StringIO(input))
|
|
rows = list(reader)
|
|
headers = list(rows[0].keys()) if rows else []
|
|
row_count = len(rows)
|
|
sample = rows[:5]
|
|
output_var: headers, row_count, sample
|
|
|
|
# AI analysis
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Analyze this CSV data:
|
|
- Columns: {headers}
|
|
- Row count: {row_count}
|
|
- Sample rows: {sample}
|
|
|
|
Provide insights about the data structure and patterns.
|
|
output_var: analysis
|
|
|
|
# Generate code
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Based on this analysis: {analysis}
|
|
|
|
Write Python code to process this CSV and extract key metrics.
|
|
output_var: code</code></pre>
|
|
|
|
<h2 id="template-composition">Template Composition</h2>
|
|
<p>Build prompts dynamically:</p>
|
|
<pre><code class="language-yaml">arguments:
|
|
- flag: --task
|
|
variable: task
|
|
default: "summarize"
|
|
|
|
steps:
|
|
- type: code
|
|
code: |
|
|
templates = {
|
|
"summarize": "Summarize this concisely:",
|
|
"explain": "Explain this for a beginner:",
|
|
"critique": "Provide constructive criticism of:",
|
|
"expand": "Expand on this with more detail:"
|
|
}
|
|
instruction = templates.get(task, templates["summarize"])
|
|
output_var: instruction
|
|
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
{instruction}
|
|
|
|
{input}
|
|
output_var: result</code></pre>
|
|
|
|
<h2 id="external-tools">Integrating External Tools</h2>
|
|
<pre><code class="language-yaml">steps:
|
|
# Use code to call external commands
|
|
- type: code
|
|
code: |
|
|
import subprocess
|
|
# Run linter
|
|
result = subprocess.run(
|
|
['pylint', '--output-format=json', '-'],
|
|
input=input,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
lint_output = result.stdout
|
|
output_var: lint_output
|
|
|
|
# AI interprets results
|
|
- type: prompt
|
|
provider: claude
|
|
prompt: |
|
|
Explain these linting results in plain English
|
|
and suggest fixes:
|
|
|
|
{lint_output}
|
|
output_var: explanation</code></pre>
|
|
|
|
<h2 id="performance-tips">Performance Tips</h2>
|
|
<ul>
|
|
<li><strong>Use fast models for simple tasks</strong> - grok for extraction, haiku for formatting</li>
|
|
<li><strong>Minimize API calls</strong> - Combine related tasks in one prompt</li>
|
|
<li><strong>Cache with code steps</strong> - Store intermediate results</li>
|
|
<li><strong>Parallel execution</strong> - See <a href="/docs/parallel-orchestration">Parallel Orchestration</a></li>
|
|
</ul>
|
|
""",
|
|
"headings": [
|
|
("multi-provider", "Multi-Provider Workflows"),
|
|
("conditional-logic", "Conditional Logic with Code"),
|
|
("iterative-refinement", "Iterative Refinement"),
|
|
("data-pipelines", "Data Processing Pipelines"),
|
|
("template-composition", "Template Composition"),
|
|
("external-tools", "Integrating External Tools"),
|
|
("performance-tips", "Performance Tips"),
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
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="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"),
|
|
]),
|
|
]
|