diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..971eef0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +# Git +.git +.gitignore + +# Python +__pycache__ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info +.eggs +*.egg +dist +build +.venv +venv +ENV + +# IDE +.idea +.vscode +*.swp + +# Docker +Dockerfile* +docker-compose* +.docker + +# Misc +*.md +!README.md +.coverage +.pytest_cache +htmlcov diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9b47766 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,80 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +SmartTools is a lightweight personal tool builder for AI-powered CLI commands. It lets users create custom terminal commands that call AI providers, chain prompts with Python code steps, and use them like any Unix pipe command. + +## Development Commands + +```bash +# Install for development +pip install -e ".[dev]" + +# Run tests +pytest + +# Run a single test +pytest tests/test.py::test_name + +# Run the CLI +python -m smarttools.cli + +# Launch the UI +smarttools ui +``` + +## Architecture + +### Core Modules (`src/smarttools/`) + +- **cli.py**: Entry point (`smarttools` command). Routes subcommands: list, create, edit, delete, test, run, ui, refresh +- **tool.py**: Tool definition dataclasses (`Tool`, `ToolArgument`, `PromptStep`, `CodeStep`), YAML config loading/saving, wrapper script generation +- **runner.py**: Execution engine. Runs tool steps sequentially, handles variable substitution (`{input}`, `{varname}`), executes Python code steps via `exec()` +- **providers.py**: Provider abstraction. Calls AI CLI tools via subprocess, reads provider configs from `~/.smarttools/providers.yaml` +- **ui.py**: UI dispatcher - selects between urwid and snack implementations +- **ui_urwid.py**: Full TUI implementation using urwid library +- **ui_snack.py**: Fallback TUI using python-newt/snack + +### Key Paths + +- **Tools storage**: `~/.smarttools//config.yaml` +- **Wrapper scripts**: `~/.local/bin/` (auto-generated bash scripts) +- **Provider config**: `~/.smarttools/providers.yaml` + +### Tool Structure + +Tools are YAML configs with: +- `name`, `description`, `category` +- `arguments`: Custom flags with defaults (e.g., `--max` → `{max}`) +- `steps`: Ordered list of `prompt` or `code` steps +- `output`: Template for final output (e.g., `"{response}"`) + +### Step Types + +1. **Prompt Step**: Calls AI provider with template, stores result in `output_var` +2. **Code Step**: Executes Python code via `exec()`, captures specified variables + +### Variable Flow + +Variables are passed between steps: +- `{input}` - always available (stdin/file content) +- `{argname}` - from tool arguments +- `{step_output_var}` - from previous step's `output_var` + +Variable substitution is simple string replacement in `runner.py:substitute_variables()`. + +## Provider System + +Providers are CLI tools that accept prompts via stdin and output to stdout. Defined in `~/.smarttools/providers.yaml`: + +```yaml +providers: + - name: claude + command: "claude -p" + - name: mock + command: "echo '[MOCK]'" +``` + +The `mock` provider is built-in for testing without API calls. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a2a4c94 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +# SmartTools - AI-powered CLI command builder +# Build: docker build -t smarttools . +# Run: docker run -it --rm -v ~/.smarttools:/root/.smarttools smarttools + +FROM python:3.12-slim + +LABEL maintainer="rob" +LABEL description="SmartTools - Personal AI-powered CLI command builder" + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Copy project files +COPY pyproject.toml README.md ./ +COPY src/ ./src/ +COPY examples/ ./examples/ +COPY docs/ ./docs/ + +# Install SmartTools and dependencies +RUN pip install --no-cache-dir -e ".[dev]" + +# Create smarttools directory +RUN mkdir -p /root/.smarttools /root/.local/bin + +# Install example tools +RUN python examples/install.py + +# Generate CLI wrappers +RUN smarttools refresh + +# Add local bin to PATH +ENV PATH="/root/.local/bin:${PATH}" + +# Default command - show help +CMD ["smarttools", "--help"] + +# For interactive use: +# docker run -it --rm smarttools smarttools ui +# docker run -it --rm smarttools bash diff --git a/README.md b/README.md index 83f8eda..5ba9797 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,59 @@ cat file.txt | mytool --dry-run cat file.txt | mytool --provider mock ``` +### Composing Tools + +SmartTools follows Unix philosophy: each tool does one thing well, and tools chain together for complex workflows. + +```bash +# Chain SmartTools together +cat error.log | log-errors | summarize | translate --lang Spanish + +# Code review pipeline: focus on changes, review, then summarize +git diff | diff-focus | review-code | tldr + +# Extract data, convert format, analyze +cat report.pdf | json-extract --fields "revenue, costs, date" | json2csv | csv-insights + +# Process text through multiple transformations +cat technical_doc.txt | simplify | fix-grammar | tone-shift --tone friendly + +# Generate code, validate it, then explain what it does +echo "parse CSV and calculate averages" | code-validate | explain-code +``` + +**Mix with standard Unix tools:** + +```bash +# Extract emails, deduplicate, count +cat inbox.txt | extract-emails | sort -u | wc -l + +# Find errors in multiple logs, explain them +cat *.log | grep -i error | explain-error + +# Generate changelog for specific author +git log --author="rob" --oneline | changelog > CHANGELOG.md + +# Review only large files in a commit +git diff --stat | awk '$3 > 100 {print $1}' | xargs cat | review-code +``` + +**Build shell functions for common pipelines:** + +```bash +# ~/.bashrc +quick-review() { + git diff "$@" | diff-focus | review-code --focus "bugs and security" | tldr +} + +translate-doc() { + cat "$1" | summarize | translate --lang "$2" +} + +# Usage: quick-review HEAD~3 +# Usage: translate-doc manual.txt French +``` + ## Example Tools SmartTools comes with 28 pre-built examples you can install: @@ -347,12 +400,17 @@ This lets you generate or modify Python code using AI directly within the tool b ## Philosophy -SmartTools is a **personal power tool**: +SmartTools is built on **Unix philosophy**: -- **You own your tools** - YAML files you can read, edit, share -- **You choose the AI** - Any provider, swap anytime -- **You accept responsibility** - No sandboxing, like any script you write -- **Unix philosophy** - Small tools that compose +1. **Do one thing well** - Each tool solves one problem. `summarize` summarizes. `translate` translates. No bloated mega-tools. + +2. **Compose freely** - Tools read stdin, write stdout. Chain them: `cat doc.txt | summarize | translate --lang French` + +3. **Text is the interface** - No proprietary formats. Pipe text in, get text out. Works with `grep`, `awk`, `sed`, and every other Unix tool. + +4. **You own everything** - Tools are YAML files. Prompts are plain text. No vendor lock-in, no cloud dependency for your tool definitions. + +5. **Swap parts freely** - Change AI providers per-tool or per-run. Today's best model might not be tomorrow's. ## Contributing diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 97aa3af..19dd73c 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -317,6 +317,50 @@ Tool names must: - Contain only letters, numbers, underscores, and dashes - Not contain spaces or shell-problematic characters (`/\|&;$`"'<>(){}[]!?*#~`) +## Tool Composition + +SmartTools are designed to chain together like any Unix command. + +### External Pipelines (Tool-to-Tool) + +```bash +# Chain multiple SmartTools +cat logs.txt | log-errors | summarize | translate --lang Japanese + +# Mix with standard Unix tools +git log --oneline | head -20 | changelog | tee CHANGELOG.md + +# Build complex workflows +cat *.py | review-code --focus security | json-extract --fields "issue, severity, file" | json2csv +``` + +Each tool reads stdin and writes stdout. No special integration needed. + +### Internal Pipelines (Multi-Step) + +Within a single tool, chain steps for preprocessing/validation: + +```yaml +steps: + - type: code # Preprocess + code: | + filtered = '\n'.join(l for l in input.split('\n') if 'ERROR' in l) + output_var: filtered + - type: prompt # AI processes filtered input + prompt: "Explain these errors: {filtered}" + provider: claude-haiku + output_var: explanation + - type: code # Post-process + code: | + result = f"Found {len(filtered.split(chr(10)))} errors:\n\n{explanation}" + output_var: result +output: "{result}" +``` + +**When to use which:** +- **External pipelines**: Reusable tools, different providers per stage, standard Unix interop +- **Internal pipelines**: Tightly coupled steps, shared context, validation of AI output + ## What This Design Doesn't Include Intentionally omitted (not needed for personal use): diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 1aecb2f..dcb1be5 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -958,3 +958,101 @@ output: "{result}" 2. **Use code steps** to validate AI output 3. **Preprocess large inputs** to reduce tokens 4. **Test with mock** before using real AI: `--provider mock` + +--- + +## Pipeline Recipes + +SmartTools chain together like Unix commands. Here are practical examples: + +### Development Workflows + +```bash +# Quick PR review: extract changes, review, summarize +git diff main | diff-focus | review-code --focus "bugs and security" | tldr + +# Explain and fix an error in one pipeline +python script.py 2>&1 | explain-error | tee error_analysis.txt + +# Generate tests for changed files only +git diff --name-only | grep '\.py$' | xargs cat | gen-tests > new_tests.py + +# Create release notes from commits +git log v1.0..v1.1 --oneline | changelog | translate --lang French > RELEASE_FR.md +``` + +### Data Processing + +```bash +# Extract, transform, analyze +curl -s api.example.com/data | json-extract --fields "users, revenue" | json2csv | csv-insights + +# Process multiple files +for f in reports/*.txt; do + cat "$f" | json-extract --fields "total, date" +done | json2csv > summary.csv + +# Clean and deduplicate contacts +cat *.vcf | extract-contacts | sort -u -t',' -k2 > contacts.csv +``` + +### Text Processing Pipelines + +```bash +# Translate technical docs for international team +cat API.md | simplify --level "non-technical" | translate --lang Spanish > API_ES.md + +# Process customer feedback +cat feedback.txt | summarize --length "10 points" | tone-shift --tone analytical | json-extract --fields "themes, sentiment" + +# Prepare content for different audiences +cat whitepaper.txt | eli5 > simple_version.txt +cat whitepaper.txt | summarize > executive_summary.txt +``` + +### Shell Functions + +Add these to `~/.bashrc` for common workflows: + +```bash +# Review recent changes +review-recent() { + git diff HEAD~"${1:-1}" | diff-focus | review-code | tldr +} + +# Quick translate with summary +translate-summary() { + cat "$1" | summarize | translate --lang "${2:-Spanish}" +} + +# Analyze any log file +analyze-log() { + cat "$1" | log-errors | summarize --length "5 key issues" +} + +# Generate commit message and commit +auto-commit() { + msg=$(git diff --staged | commit-msg) + echo "Commit message: $msg" + read -p "Commit? [y/N] " confirm + [[ $confirm == [yY] ]] && git commit -m "$msg" +} +``` + +### Combining with Standard Unix Tools + +```bash +# Filter before AI processing (saves tokens) +cat huge.log | grep -i error | tail -100 | explain-error + +# Post-process AI output +cat doc.txt | summarize | fmt -w 80 > formatted_summary.txt + +# Parallel processing +find . -name "*.py" | parallel -j4 'cat {} | review-code > {}.review' + +# Watch and process +tail -f app.log | grep --line-buffered ERROR | while read line; do + echo "$line" | explain-error +done +``` diff --git a/src/smarttools/cli.py b/src/smarttools/cli.py index 79c4ee9..0d14001 100644 --- a/src/smarttools/cli.py +++ b/src/smarttools/cli.py @@ -263,6 +263,79 @@ def cmd_refresh(args): return 0 +def cmd_docs(args): + """View or edit tool documentation.""" + import os + import subprocess + + from .tool import get_tools_dir + + tool = load_tool(args.name) + if not tool: + print(f"Error: Tool '{args.name}' not found.") + return 1 + + readme_path = get_tools_dir() / args.name / "README.md" + + if args.edit: + # Edit/create README + editor = os.environ.get("EDITOR", "nano") + + # Create a template if README doesn't exist + if not readme_path.exists(): + template = f"""# {args.name} + +{tool.description or 'No description provided.'} + +## Usage + +```bash +echo "input" | {args.name} +``` + +## Arguments + +| Flag | Default | Description | +|------|---------|-------------| +""" + for arg in tool.arguments: + template += f"| `{arg.flag}` | {arg.default or ''} | {arg.description or ''} |\n" + + template += """ +## Examples + +```bash +# Example 1 +``` + +## Requirements + +- List any dependencies here +""" + readme_path.write_text(template) + print(f"Created template: {readme_path}") + + try: + subprocess.run([editor, str(readme_path)], check=True) + print(f"Documentation updated: {readme_path}") + return 0 + except subprocess.CalledProcessError: + print("Error: Editor failed.") + return 1 + except FileNotFoundError: + print(f"Error: Editor '{editor}' not found. Set $EDITOR environment variable.") + return 1 + else: + # View README + if not readme_path.exists(): + print(f"No documentation found for '{args.name}'.") + print(f"Create it with: smarttools docs {args.name} --edit") + return 1 + + print(readme_path.read_text()) + return 0 + + def main(): """Main CLI entry point.""" parser = argparse.ArgumentParser( @@ -326,6 +399,12 @@ def main(): p_refresh = subparsers.add_parser("refresh", help="Refresh all wrapper scripts") p_refresh.set_defaults(func=cmd_refresh) + # 'docs' command + p_docs = subparsers.add_parser("docs", help="View or edit tool documentation") + p_docs.add_argument("name", help="Tool name") + p_docs.add_argument("-e", "--edit", action="store_true", help="Edit/create README in $EDITOR") + p_docs.set_defaults(func=cmd_docs) + args = parser.parse_args() # If no command, launch UI diff --git a/tests/diagram.puml b/tests/diagram.puml new file mode 100644 index 0000000..b30a403 --- /dev/null +++ b/tests/diagram.puml @@ -0,0 +1,10 @@ +@startuml +start +:Set a = 5; +:Set b = 6; +:Set c = 7; +:Calculate semi-perimeter s = (a + b + c) / 2; +:Calculate area = sqrt(s*(s-a)*(s-b)*(s-c)); +:Print "The area of the triangle is [area]"; +stop +@enduml diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..bef854f --- /dev/null +++ b/tests/test.py @@ -0,0 +1,17 @@ +# Python Program to find the area of triangle + +a = 5 +b = 6 +c = 7 + +# Uncomment below to take inputs from the user +# a = float(input('Enter first side: ')) +# b = float(input('Enter second side: ')) +# c = float(input('Enter third side: ')) + +# calculate the semi-perimeter +s = (a + b + c) / 2 + +# calculate the area +area = (s*(s-a)*(s-b)*(s-c)) ** 0.5 +print('The area of the triangle is %0.2f' %area)