diff --git a/config.yaml b/config.yaml index 8e636e1..ca7a265 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,7 @@ # 配置文件 app: secret_key: ${APP_SECRET_KEY} - debug: false + debug: true host: 0.0.0.0 port: 8000 diff --git a/luxx/database.py b/luxx/database.py index 04092c3..c4a9dba 100644 --- a/luxx/database.py +++ b/luxx/database.py @@ -10,7 +10,7 @@ from luxx.config import config engine = create_engine( config.database_url, connect_args={"check_same_thread": False} if "sqlite" in config.database_url else {}, - echo=config.debug + echo=False ) # Create session factory diff --git a/luxx/tools/builtin/__init__.py b/luxx/tools/builtin/__init__.py index 03d886f..3271d54 100644 --- a/luxx/tools/builtin/__init__.py +++ b/luxx/tools/builtin/__init__.py @@ -4,5 +4,6 @@ from luxx.tools.builtin import crawler from luxx.tools.builtin import code from luxx.tools.builtin import data from luxx.tools.builtin import file +from luxx.tools.builtin import shell -__all__ = ["crawler", "code", "data", "file"] +__all__ = ["crawler", "code", "data", "file", "shell"] diff --git a/luxx/tools/builtin/shell.py b/luxx/tools/builtin/shell.py new file mode 100644 index 0000000..a70709e --- /dev/null +++ b/luxx/tools/builtin/shell.py @@ -0,0 +1,105 @@ +"""Shell command execution tool""" +import subprocess +from typing import Dict, Any + +from luxx.tools.factory import tool +from luxx.tools.core import ToolContext, CommandPermission +from luxx.config import config +from pathlib import Path + +def get_workspace_from_context(context: ToolContext) -> str: + """Get workspace from context, auto-create if needed""" + import hashlib + from pathlib import Path + + if context.workspace: + return context.workspace + if context.user_id is not None: + workspace_root = Path(config.workspace_root).resolve() + user_hash = hashlib.sha256(str(context.user_id).encode()).hexdigest()[:16] + user_workspace = workspace_root / user_hash + if config.workspace_auto_create and not user_workspace.exists(): + user_workspace.mkdir(parents=True, exist_ok=True) + return str(user_workspace) + return None + + +@tool( + name="shell_exec", + description="Execute a shell command in the user's workspace directory", + parameters={ + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Shell command to execute" + }, + "timeout": { + "type": "integer", + "description": "Timeout in seconds (default 30)", + "default": 30 + }, + "cwd": { + "type": "string", + "description": "Working directory relative to workspace (default: workspace root)", + "default": "." + } + }, + "required": ["command"] + }, + required_params=["command"], + category="shell", + required_permission=CommandPermission.EXECUTE +) +def shell_exec(arguments: Dict[str, Any], context: ToolContext = None): + """ + Execute a shell command in the user's workspace. + + Args: + command: Shell command to execute + timeout: Timeout in seconds (default 30) + cwd: Working directory relative to workspace (default: workspace root) + + Returns: + {"stdout": str, "stderr": str, "returncode": int} + """ + workspace = get_workspace_from_context(context) + + if not workspace: + return {"error": "Workspace not configured"} + + command = arguments.get("command", "") + timeout = arguments.get("timeout", 30) + cwd_rel = arguments.get("cwd", ".") + + # Build working directory + + + workspace_path = Path(workspace).resolve() + cwd_path = (workspace_path / cwd_rel).resolve() + + # Security check: ensure cwd is within workspace + if not cwd_path.is_relative_to(workspace_path): + return {"error": "Working directory is outside workspace"} + + try: + result = subprocess.run( + command, + shell=True, + cwd=str(cwd_path), + capture_output=True, + text=True, + timeout=min(timeout, 120) # Max 2 minutes + ) + + return { + "stdout": result.stdout, + "stderr": result.stderr, + "returncode": result.returncode, + "command": command, + "cwd": str(cwd_path.relative_to(workspace_path)) + } + except subprocess.TimeoutExpired: + return {"error": f"Command timed out after {timeout} seconds"} + except Exception as e: + return {"error": f"Execution error: {str(e)}"}