""" Tool Forge — Creates new Python tools from natural language specifications. Generates standalone, safe, importable tool modules. """ import os import re import json import textwrap from pathlib import Path from registry import ToolRegistry, TOOLS_DIR TOOL_TEMPLATE = '''\ """ {description} Auto-generated by Tool Forge. Parameters: {params_doc} """ import json import sys {extra_imports} def run({param_signature}): """{description}""" {body} def main(): """CLI entry point.""" import argparse parser = argparse.ArgumentParser(description="{description}") {argparse_args} args = parser.parse_args() result = run(**vars(args)) if result is not None: if isinstance(result, (dict, list)): print(json.dumps(result, indent=2, default=str)) else: print(result) if __name__ == "__main__": main() ''' class ToolForge: def __init__(self, registry: ToolRegistry = None): self.registry = registry or ToolRegistry() def create_tool(self, name: str, description: str, params: dict = None, body: str = None, imports: list = None, tags: list = None) -> dict: """ Create a new tool and register it. Args: name: Tool name (snake_case) description: What the tool does params: Dict of {param_name: {"type": str, "desc": str, "default": any}} body: Python function body (indented 4 spaces). If None, generates a stub. imports: Extra import lines tags: Searchable tags """ name = re.sub(r'[^a-z0-9_]', '_', name.lower().replace('-', '_').replace(' ', '_')) name = re.sub(r'_+', '_', name).strip('_') params = params or {} tags = tags or [] imports = imports or [] # Build parameter signature required = {k: v for k, v in params.items() if "default" not in v} optional = {k: v for k, v in params.items() if "default" in v} sig_parts = list(required.keys()) for k, v in optional.items(): sig_parts.append(f"{k}={repr(v['default'])}") param_signature = ", ".join(sig_parts) if sig_parts else "" # Params doc params_doc = ", ".join(f"{k} ({v.get('type', 'any')}): {v.get('desc', '')}" for k, v in params.items()) or "none" # Argparse args argparse_lines = [] for k, v in params.items(): type_str = v.get("type", "str") py_type = {"str": "str", "int": "int", "float": "float", "bool": "bool"}.get(type_str, "str") if "default" in v: argparse_lines.append( f' parser.add_argument("--{k}", type={py_type}, default={repr(v["default"])}, help="{v.get("desc", "")}")') else: argparse_lines.append( f' parser.add_argument("{k}", type={py_type}, help="{v.get("desc", "")}")') argparse_args = "\n".join(argparse_lines) if argparse_lines else ' pass' # Body if not body: body = f' # TODO: Implement {name}\n raise NotImplementedError("Tool {name} needs implementation")' extra_imports = "\n".join(f"import {i}" for i in imports) source = TOOL_TEMPLATE.format( description=description, params_doc=params_doc, param_signature=param_signature, body=body, extra_imports=extra_imports, argparse_args=argparse_args, ) # Write tool file tool_path = os.path.join(TOOLS_DIR, f"{name}.py") Path(tool_path).write_text(source) os.chmod(tool_path, 0o755) # Register in RAG self.registry.register(name, description, tool_path, params, tags + ["auto-created"]) return { "name": name, "path": tool_path, "description": description, "params": params, } def ensure_tool(self, query: str, auto_create: bool = True, params: dict = None, body: str = None) -> dict: """ Search for existing tool; create one if not found and auto_create is True. Returns tool metadata. """ matches = self.registry.search(query, n=3, threshold=0.5) if matches: return matches[0] if not auto_create: return None # Derive a name from the query name = re.sub(r'[^a-z0-9 ]', '', query.lower()) name = '_'.join(name.split()[:5]) return self.create_tool( name=name, description=query, params=params, body=body, )