#!/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()