feat: 增加shell操作
This commit is contained in:
parent
99e7de9efd
commit
22a4b8a4bb
|
|
@ -1,7 +1,7 @@
|
||||||
# 配置文件
|
# 配置文件
|
||||||
app:
|
app:
|
||||||
secret_key: ${APP_SECRET_KEY}
|
secret_key: ${APP_SECRET_KEY}
|
||||||
debug: false
|
debug: true
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
port: 8000
|
port: 8000
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from luxx.config import config
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
config.database_url,
|
config.database_url,
|
||||||
connect_args={"check_same_thread": False} if "sqlite" in config.database_url else {},
|
connect_args={"check_same_thread": False} if "sqlite" in config.database_url else {},
|
||||||
echo=config.debug
|
echo=False
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create session factory
|
# Create session factory
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@ from luxx.tools.builtin import crawler
|
||||||
from luxx.tools.builtin import code
|
from luxx.tools.builtin import code
|
||||||
from luxx.tools.builtin import data
|
from luxx.tools.builtin import data
|
||||||
from luxx.tools.builtin import file
|
from luxx.tools.builtin import file
|
||||||
|
from luxx.tools.builtin import shell
|
||||||
|
|
||||||
__all__ = ["crawler", "code", "data", "file"]
|
__all__ = ["crawler", "code", "data", "file", "shell"]
|
||||||
|
|
|
||||||
|
|
@ -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)}"}
|
||||||
Loading…
Reference in New Issue