Implement core SmartTools functionality

- providers.py: Provider abstraction with mock support
- tool.py: Tool loading, saving, and wrapper script generation
- runner.py: Tool execution engine with prompt building
- ui.py: Dialog-based UI for tool management
- cli.py: CLI entry point with list/create/edit/delete/test commands

~430 lines of Python as designed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
rob
2025-11-28 04:45:39 -04:00
co-authored by Claude
parent ba18c0feaa
commit 28dac465e6
7 changed files with 1167 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
"""Provider abstraction for AI CLI tools."""
import subprocess
import shutil
from dataclasses import dataclass
from typing import Optional
@dataclass
class ProviderResult:
"""Result from a provider call."""
text: str
success: bool
error: Optional[str] = None
def call_provider(provider: str, args: str, prompt: str, timeout: int = 300) -> ProviderResult:
"""
Call an AI CLI tool with the given prompt.
Args:
provider: The CLI command (e.g., 'codex', 'claude', 'gemini')
args: Additional arguments for the provider (e.g., '-p')
prompt: The prompt to send
timeout: Maximum execution time in seconds
Returns:
ProviderResult with the response text or error
"""
# Check if provider exists
if not shutil.which(provider):
return ProviderResult(
text="",
success=False,
error=f"Provider '{provider}' not found. Is it installed and in PATH?"
)
# Build command
cmd = f"{provider} {args}".strip()
try:
result = subprocess.run(
cmd,
shell=True,
input=prompt,
capture_output=True,
text=True,
timeout=timeout
)
if result.returncode != 0:
return ProviderResult(
text="",
success=False,
error=f"Provider exited with code {result.returncode}: {result.stderr}"
)
return ProviderResult(text=result.stdout, success=True)
except subprocess.TimeoutExpired:
return ProviderResult(
text="",
success=False,
error=f"Provider timed out after {timeout} seconds"
)
except Exception as e:
return ProviderResult(
text="",
success=False,
error=f"Provider error: {str(e)}"
)
def mock_provider(prompt: str) -> ProviderResult:
"""
Return a mock response for testing.
Args:
prompt: The prompt (used for generating mock response)
Returns:
ProviderResult with mock response
"""
lines = prompt.strip().split('\n')
preview = lines[0][:50] + "..." if len(lines[0]) > 50 else lines[0]
return ProviderResult(
text=f"[MOCK RESPONSE]\n"
f"Prompt length: {len(prompt)} chars, {len(lines)} lines\n"
f"First line: {preview}\n"
f"\n"
f"This is a mock response. Use a real provider for actual output.",
success=True
)
def get_provider_func(provider: str):
"""
Get the appropriate provider function.
Args:
provider: Provider name ('mock' for mock, anything else for real)
Returns:
Callable that takes (prompt) or (provider, args, prompt)
"""
if provider.lower() == "mock":
return lambda prompt, **kwargs: mock_provider(prompt)
else:
return lambda prompt, args="", **kwargs: call_provider(provider, args, prompt, **kwargs)