343 lines
19 KiB
Python
343 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Install all SmartTools example tools.
|
|
|
|
Usage:
|
|
python3 install.py # Install all tools
|
|
python3 install.py --list # List available tools
|
|
python3 install.py summarize # Install specific tool
|
|
python3 install.py --category dev # Install category
|
|
|
|
Run from anywhere:
|
|
curl -sSL https://raw.githubusercontent.com/yourusername/smarttools/main/examples/install.py | python3
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import yaml
|
|
except ImportError:
|
|
print("PyYAML required. Install with: pip install pyyaml")
|
|
sys.exit(1)
|
|
|
|
TOOLS_DIR = Path.home() / ".smarttools"
|
|
|
|
# All example tool configurations
|
|
TOOLS = {
|
|
# TEXT PROCESSING
|
|
"summarize": {
|
|
"category": "text",
|
|
"description": "Condense long documents to key points",
|
|
"arguments": [{"flag": "--length", "variable": "length", "default": "3-5 bullet points"}],
|
|
"steps": [{"type": "prompt", "prompt": "Summarize the following text into {length}. Be concise and capture the key points:\n\n{input}", "provider": "opencode-pickle", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"translate": {
|
|
"category": "text",
|
|
"description": "Translate text to any language",
|
|
"arguments": [{"flag": "--lang", "variable": "lang", "default": "Spanish"}],
|
|
"steps": [{"type": "prompt", "prompt": "Translate the following text to {lang}. Only output the translation, nothing else:\n\n{input}", "provider": "claude-haiku", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"fix-grammar": {
|
|
"category": "text",
|
|
"description": "Fix grammar and spelling errors",
|
|
"arguments": [],
|
|
"steps": [{"type": "prompt", "prompt": "Fix all grammar, spelling, and punctuation errors in the following text. Only output the corrected text, no explanations:\n\n{input}", "provider": "opencode-deepseek", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"simplify": {
|
|
"category": "text",
|
|
"description": "Rewrite text for easier understanding",
|
|
"arguments": [{"flag": "--level", "variable": "level", "default": "5th grade reading level"}],
|
|
"steps": [{"type": "prompt", "prompt": "Rewrite the following text for a {level}. Keep the meaning but use simpler words and shorter sentences:\n\n{input}", "provider": "opencode-pickle", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"tone-shift": {
|
|
"category": "text",
|
|
"description": "Change the tone of text",
|
|
"arguments": [{"flag": "--tone", "variable": "tone", "default": "professional"}],
|
|
"steps": [{"type": "prompt", "prompt": "Rewrite the following text in a {tone} tone. Keep the core message but adjust the style:\n\n{input}", "provider": "opencode-deepseek", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"eli5": {
|
|
"category": "text",
|
|
"description": "Explain like I'm 5",
|
|
"arguments": [],
|
|
"steps": [{"type": "prompt", "prompt": "Explain this like I'm 5 years old. Use simple words and fun analogies:\n\n{input}", "provider": "opencode-pickle", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"tldr": {
|
|
"category": "text",
|
|
"description": "One-line summary",
|
|
"arguments": [],
|
|
"steps": [{"type": "prompt", "prompt": "Give a one-line TL;DR summary of this text:\n\n{input}", "provider": "opencode-grok", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"expand": {
|
|
"category": "text",
|
|
"description": "Expand bullet points to paragraphs",
|
|
"arguments": [],
|
|
"steps": [{"type": "prompt", "prompt": "Expand these bullet points into well-written paragraphs:\n\n{input}", "provider": "opencode-pickle", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
|
|
# DEVELOPER TOOLS
|
|
"explain-error": {
|
|
"category": "dev",
|
|
"description": "Explain error messages and stack traces",
|
|
"arguments": [],
|
|
"steps": [{"type": "prompt", "prompt": "Explain this error/stack trace in plain English. What went wrong and how to fix it:\n\n{input}", "provider": "claude-haiku", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"explain-code": {
|
|
"category": "dev",
|
|
"description": "Explain what code does",
|
|
"arguments": [{"flag": "--detail", "variable": "detail", "default": "moderate"}],
|
|
"steps": [{"type": "prompt", "prompt": "Explain what this code does at a {detail} level of detail:\n\n```\n{input}\n```", "provider": "opencode-pickle", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"review-code": {
|
|
"category": "dev",
|
|
"description": "Quick code review with suggestions",
|
|
"arguments": [{"flag": "--focus", "variable": "focus", "default": "bugs, security, and improvements"}],
|
|
"steps": [{"type": "prompt", "prompt": "Review this code focusing on {focus}. Be concise and actionable:\n\n```\n{input}\n```", "provider": "claude-sonnet", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"gen-tests": {
|
|
"category": "dev",
|
|
"description": "Generate unit tests for code",
|
|
"arguments": [{"flag": "--framework", "variable": "framework", "default": "pytest"}],
|
|
"steps": [{"type": "prompt", "prompt": "Generate comprehensive unit tests for this code using {framework}. Include edge cases:\n\n```\n{input}\n```", "provider": "claude-haiku", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"docstring": {
|
|
"category": "dev",
|
|
"description": "Add docstrings to functions/classes",
|
|
"arguments": [{"flag": "--style", "variable": "style", "default": "Google style"}],
|
|
"steps": [{"type": "prompt", "prompt": "Add {style} docstrings to all functions and classes in this code. Output the complete code with docstrings:\n\n```\n{input}\n```", "provider": "opencode-deepseek", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"commit-msg": {
|
|
"category": "dev",
|
|
"description": "Generate commit message from diff",
|
|
"arguments": [{"flag": "--style", "variable": "style", "default": "conventional commits"}],
|
|
"steps": [{"type": "prompt", "prompt": "Generate a concise {style} commit message for this diff. Just the message, no explanation:\n\n{input}", "provider": "opencode-pickle", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
|
|
# DATA TOOLS
|
|
"json-extract": {
|
|
"category": "data",
|
|
"description": "Extract structured data as validated JSON",
|
|
"arguments": [{"flag": "--fields", "variable": "fields", "default": "any relevant fields"}],
|
|
"steps": [
|
|
{"type": "prompt", "prompt": "Extract {fields} from this text as a JSON object. Output ONLY valid JSON, no markdown, no explanation:\n\n{input}", "provider": "opencode-deepseek", "output_var": "raw_json"},
|
|
{"type": "code", "code": "import json\nimport re\ntext = raw_json.strip()\ntext = re.sub(r'^```json?\\s*', '', text)\ntext = re.sub(r'\\s*```$', '', text)\ntry:\n parsed = json.loads(text)\n validated = json.dumps(parsed, indent=2)\nexcept json.JSONDecodeError as e:\n validated = f\"ERROR: Invalid JSON - {e}\\nRaw output: {text[:500]}\"", "output_var": "validated"}
|
|
],
|
|
"output": "{validated}"
|
|
},
|
|
"json2csv": {
|
|
"category": "data",
|
|
"description": "Convert JSON to CSV format",
|
|
"arguments": [],
|
|
"steps": [{"type": "prompt", "prompt": "Convert this JSON to CSV format. Output only the CSV, no explanation:\n\n{input}", "provider": "opencode-deepseek", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"extract-emails": {
|
|
"category": "data",
|
|
"description": "Extract email addresses from text",
|
|
"arguments": [],
|
|
"steps": [{"type": "prompt", "prompt": "Extract all email addresses from this text. Output one email per line, nothing else:\n\n{input}", "provider": "opencode-pickle", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"extract-contacts": {
|
|
"category": "data",
|
|
"description": "Extract contact info as structured CSV",
|
|
"arguments": [],
|
|
"steps": [
|
|
{"type": "prompt", "prompt": "Extract all contact information (name, email, phone, company) from this text. Output as JSON array with objects having keys: name, email, phone, company. Use null for missing fields. Output ONLY the JSON array:\n\n{input}", "provider": "opencode-pickle", "output_var": "contacts_json"},
|
|
{"type": "code", "code": "import json\nimport re\ntext = contacts_json.strip()\ntext = re.sub(r'^```json?\\s*', '', text)\ntext = re.sub(r'\\s*```$', '', text)\ntry:\n contacts = json.loads(text)\n lines = ['name,email,phone,company']\n for c in contacts:\n row = [str(c.get('name') or ''), str(c.get('email') or ''), str(c.get('phone') or ''), str(c.get('company') or '')]\n lines.append(','.join(f'\"{f}\"' for f in row))\n csv_output = '\\n'.join(lines)\nexcept:\n csv_output = f\"Error parsing contacts: {text[:200]}\"", "output_var": "csv_output"}
|
|
],
|
|
"output": "{csv_output}"
|
|
},
|
|
"sql-from-text": {
|
|
"category": "data",
|
|
"description": "Generate SQL from natural language",
|
|
"arguments": [{"flag": "--dialect", "variable": "dialect", "default": "PostgreSQL"}],
|
|
"steps": [{"type": "prompt", "prompt": "Generate a {dialect} SQL query for this request. Output only the SQL, no explanation:\n\n{input}", "provider": "claude-haiku", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"safe-sql": {
|
|
"category": "data",
|
|
"description": "Generate SQL with safety checks",
|
|
"arguments": [{"flag": "--dialect", "variable": "dialect", "default": "PostgreSQL"}],
|
|
"steps": [
|
|
{"type": "prompt", "prompt": "Generate a {dialect} SELECT query for: {input}\n\nOutput ONLY the SQL query, no explanation.", "provider": "claude-haiku", "output_var": "raw_sql"},
|
|
{"type": "code", "code": "import re\nsql = raw_sql.strip()\nsql = re.sub(r'^```sql?\\s*', '', sql)\nsql = re.sub(r'\\s*```$', '', sql)\ndangerous = ['DROP', 'DELETE', 'TRUNCATE', 'UPDATE', 'INSERT', 'ALTER', 'CREATE', 'GRANT']\nwarnings = []\nfor keyword in dangerous:\n if re.search(r'\\b' + keyword + r'\\b', sql, re.I):\n warnings.append(f\"WARNING: Contains {keyword}\")\nif warnings:\n validated = '\\n'.join(warnings) + '\\n\\n' + sql\nelse:\n validated = sql", "output_var": "validated"}
|
|
],
|
|
"output": "{validated}"
|
|
},
|
|
"parse-log": {
|
|
"category": "data",
|
|
"description": "Summarize and analyze log files",
|
|
"arguments": [{"flag": "--focus", "variable": "focus", "default": "errors and warnings"}],
|
|
"steps": [{"type": "prompt", "prompt": "Analyze these logs focusing on {focus}. Summarize key issues and patterns:\n\n{input}", "provider": "claude-haiku", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
"csv-insights": {
|
|
"category": "data",
|
|
"description": "Analyze CSV with sampled data for large files",
|
|
"arguments": [{"flag": "--question", "variable": "question", "default": "What patterns and insights can you find?"}],
|
|
"steps": [
|
|
{"type": "code", "code": "import random\nlines = input.strip().split('\\n')\nheader = lines[0] if lines else ''\ndata_lines = lines[1:] if len(lines) > 1 else []\nif len(data_lines) > 50:\n sampled = random.sample(data_lines, 50)\n sample_note = f\"(Sampled 50 of {len(data_lines)} rows)\"\nelse:\n sampled = data_lines\n sample_note = f\"({len(data_lines)} rows)\"\nsampled_csv = header + '\\n' + '\\n'.join(sampled)\nstats = f\"Columns: {len(header.split(','))}, Rows: {len(data_lines)} {sample_note}\"", "output_var": "sampled_csv,stats"},
|
|
{"type": "prompt", "prompt": "Analyze this CSV data. {stats}\n\nQuestion: {question}\n\nData:\n{sampled_csv}", "provider": "claude-haiku", "output_var": "response"}
|
|
],
|
|
"output": "{response}"
|
|
},
|
|
|
|
# ADVANCED TOOLS
|
|
"log-errors": {
|
|
"category": "advanced",
|
|
"description": "Extract and explain errors from large log files",
|
|
"arguments": [],
|
|
"steps": [
|
|
{"type": "code", "code": "import re\nlines = input.split('\\n')\nerrors = [l for l in lines if re.search(r'\\b(ERROR|CRITICAL|FATAL|Exception|Traceback)\\b', l, re.I)]\nresult = []\nfor i, line in enumerate(lines):\n if re.search(r'\\b(ERROR|CRITICAL|FATAL|Exception|Traceback)\\b', line, re.I):\n result.extend(lines[i:i+5])\nextracted = '\\n'.join(result[:200])", "output_var": "extracted"},
|
|
{"type": "prompt", "prompt": "Analyze these error log entries. Group by error type, explain likely causes, and suggest fixes:\n\n{extracted}", "provider": "claude-haiku", "output_var": "response"}
|
|
],
|
|
"output": "{response}"
|
|
},
|
|
"diff-focus": {
|
|
"category": "advanced",
|
|
"description": "Review only the added/changed code in a diff",
|
|
"arguments": [],
|
|
"steps": [
|
|
{"type": "code", "code": "lines = input.split('\\n')\nresult = []\nfor i, line in enumerate(lines):\n if line.startswith('@@') or line.startswith('+++') or line.startswith('---'):\n result.append(line)\n elif line.startswith('+') and not line.startswith('+++'):\n result.append(line)\nextracted = '\\n'.join(result)", "output_var": "extracted"},
|
|
{"type": "prompt", "prompt": "Review these added lines of code. Focus on bugs, security issues, and improvements:\n\n{extracted}", "provider": "claude-haiku", "output_var": "response"}
|
|
],
|
|
"output": "{response}"
|
|
},
|
|
"changelog": {
|
|
"category": "advanced",
|
|
"description": "Generate changelog from git commits",
|
|
"arguments": [{"flag": "--since", "variable": "since", "default": "last week"}],
|
|
"steps": [
|
|
{"type": "code", "code": "lines = input.strip().split('\\n')\ncommits = []\nfor line in lines:\n if line.strip() and not line.startswith('commit '):\n commits.append(line.strip())\ngit_log = '\\n'.join(commits[:100])", "output_var": "git_log"},
|
|
{"type": "prompt", "prompt": "Generate a user-friendly changelog from these git commits. Group by category (Features, Fixes, Improvements, etc). Use markdown with bullet points:\n\n{git_log}", "provider": "opencode-pickle", "output_var": "changelog_raw"},
|
|
{"type": "code", "code": "from datetime import datetime\nheader = f\"\"\"# Changelog\\n\\nGenerated: {datetime.now().strftime('%Y-%m-%d')}\\n\\n\"\"\"\nformatted = header + changelog_raw", "output_var": "formatted"}
|
|
],
|
|
"output": "{formatted}"
|
|
},
|
|
"code-validate": {
|
|
"category": "advanced",
|
|
"description": "Generate and validate Python code",
|
|
"arguments": [{"flag": "--task", "variable": "task", "default": ""}],
|
|
"steps": [
|
|
{"type": "prompt", "prompt": "Write Python code to: {task}\n\nContext/input data:\n{input}\n\nOutput ONLY the Python code, no markdown, no explanation.", "provider": "claude-haiku", "output_var": "raw_code"},
|
|
{"type": "code", "code": "import ast\nimport re\ncode = raw_code.strip()\ncode = re.sub(r'^```python?\\s*', '', code)\ncode = re.sub(r'\\s*```$', '', code)\ntry:\n ast.parse(code)\n validated = code\nexcept SyntaxError as e:\n validated = f\"# SYNTAX ERROR at line {e.lineno}: {e.msg}\\n# Fix needed:\\n\\n{code}\"", "output_var": "validated"}
|
|
],
|
|
"output": "{validated}"
|
|
},
|
|
"reply-email": {
|
|
"category": "text",
|
|
"description": "Draft email replies",
|
|
"arguments": [{"flag": "--tone", "variable": "tone", "default": "professional and friendly"}],
|
|
"steps": [{"type": "prompt", "prompt": "Draft a {tone} reply to this email:\n\n{input}", "provider": "opencode-deepseek", "output_var": "response"}],
|
|
"output": "{response}"
|
|
},
|
|
}
|
|
|
|
CATEGORIES = {
|
|
"text": "Text Processing",
|
|
"dev": "Developer Tools",
|
|
"data": "Data Tools",
|
|
"advanced": "Advanced Multi-Step Tools",
|
|
}
|
|
|
|
|
|
def install_tool(name: str, config: dict, force: bool = False) -> bool:
|
|
"""Install a single tool."""
|
|
tool_dir = TOOLS_DIR / name
|
|
config_file = tool_dir / "config.yaml"
|
|
|
|
if config_file.exists() and not force:
|
|
print(f" {name}: already exists (use --force to overwrite)")
|
|
return False
|
|
|
|
tool_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Build config without category
|
|
tool_config = {
|
|
"name": name,
|
|
"description": config["description"],
|
|
"arguments": config.get("arguments", []),
|
|
"steps": config["steps"],
|
|
"output": config["output"]
|
|
}
|
|
|
|
with open(config_file, 'w') as f:
|
|
yaml.dump(tool_config, f, default_flow_style=False, sort_keys=False)
|
|
|
|
print(f" {name}: installed")
|
|
return True
|
|
|
|
|
|
def list_tools():
|
|
"""List all available tools."""
|
|
print("Available example tools:\n")
|
|
|
|
for cat_id, cat_name in CATEGORIES.items():
|
|
tools_in_cat = [(n, c) for n, c in TOOLS.items() if c.get("category") == cat_id]
|
|
if tools_in_cat:
|
|
print(f"{cat_name}:")
|
|
for name, config in tools_in_cat:
|
|
print(f" {name:<20} {config['description']}")
|
|
print()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Install SmartTools example tools")
|
|
parser.add_argument("tools", nargs="*", help="Specific tools to install (default: all)")
|
|
parser.add_argument("--list", action="store_true", help="List available tools")
|
|
parser.add_argument("--category", "-c", choices=list(CATEGORIES.keys()), help="Install tools from category")
|
|
parser.add_argument("--force", "-f", action="store_true", help="Overwrite existing tools")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.list:
|
|
list_tools()
|
|
return
|
|
|
|
# Determine which tools to install
|
|
if args.tools:
|
|
to_install = {n: c for n, c in TOOLS.items() if n in args.tools}
|
|
unknown = set(args.tools) - set(TOOLS.keys())
|
|
if unknown:
|
|
print(f"Unknown tools: {', '.join(unknown)}")
|
|
print("Use --list to see available tools")
|
|
return
|
|
elif args.category:
|
|
to_install = {n: c for n, c in TOOLS.items() if c.get("category") == args.category}
|
|
else:
|
|
to_install = TOOLS
|
|
|
|
if not to_install:
|
|
print("No tools to install")
|
|
return
|
|
|
|
print(f"Installing {len(to_install)} tools to {TOOLS_DIR}...\n")
|
|
|
|
installed = 0
|
|
for name, config in to_install.items():
|
|
if install_tool(name, config, args.force):
|
|
installed += 1
|
|
|
|
print(f"\nInstalled {installed} tools.")
|
|
print("\nRun 'smarttools refresh' to create executable wrappers.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|