Skip to main content
Glama
commands.py7.4 kB
import subprocess import tempfile import os import shutil from pathlib import Path from typing import Optional, Union, List, Tuple import dotenv from .exceptions import ( AsepriteNotFoundError, CommandExecutionError, LuaScriptError, FileNotFoundError as AsepriteFileNotFoundError ) from .validation import validate_file_path from .config import get_config from .logging import get_logger, Timer dotenv.load_dotenv() logger = get_logger(__name__) class AsepriteCommand: """Helper class for running Aseprite commands.""" def __init__(self): """Initialize AsepriteCommand with validated executable path.""" self.config = get_config() logger.info("Initializing AsepriteCommand") self._aseprite_path = self._find_aseprite() logger.info(f"Found Aseprite at: {self._aseprite_path}") def _find_aseprite(self) -> str: """Find Aseprite executable path. Returns: str: Path to Aseprite executable Raises: AsepriteNotFoundError: If Aseprite cannot be found """ # First check configuration if self.config.aseprite_path: if os.path.isfile(self.config.aseprite_path) and os.access(self.config.aseprite_path, os.X_OK): return self.config.aseprite_path else: raise AsepriteNotFoundError(self.config.aseprite_path) # Then check environment variable env_path = os.getenv('ASEPRITE_PATH') if env_path: if os.path.isfile(env_path) and os.access(env_path, os.X_OK): return env_path else: raise AsepriteNotFoundError(env_path) # Check if aseprite is in PATH aseprite_in_path = shutil.which('aseprite') if aseprite_in_path: return aseprite_in_path # Check common installation locations common_paths = [ r"C:\Program Files\Aseprite\Aseprite.exe", r"C:\Program Files (x86)\Aseprite\Aseprite.exe", "/Applications/Aseprite.app/Contents/MacOS/aseprite", "/usr/bin/aseprite", "/usr/local/bin/aseprite" ] for path in common_paths: if os.path.isfile(path) and os.access(path, os.X_OK): return path raise AsepriteNotFoundError() def run_command(self, args: List[str]) -> Tuple[bool, str]: """Run an Aseprite command with proper error handling. Args: args: List of command arguments Returns: tuple: (success, output) where success is a boolean and output is the command output Raises: CommandExecutionError: If command execution fails """ cmd = [self._aseprite_path] + args with Timer("run_command", command=' '.join(args[:3] + ['...'] if len(args) > 3 else args)): try: logger.debug(f"Running command: {' '.join(cmd)}") result = subprocess.run( cmd, check=True, capture_output=True, text=True, timeout=self.config.lua.timeout ) logger.debug(f"Command completed successfully") return True, result.stdout except subprocess.CalledProcessError as e: logger.error(f"Command failed with exit code {e.returncode}", command=cmd, stderr=e.stderr) raise CommandExecutionError(cmd, e.returncode, e.stderr or e.stdout) except subprocess.TimeoutExpired: logger.error(f"Command timed out", command=cmd, timeout=self.config.lua.timeout) raise CommandExecutionError(cmd, -1, f"Command timed out after {self.config.lua.timeout} seconds") def execute_lua_script(self, script_content: str, filename: Optional[str] = None) -> Tuple[bool, str]: """Execute a Lua script in Aseprite. Args: script_content: Lua script code to execute filename: Optional filename to open before executing script Returns: tuple: (success, output) Raises: LuaScriptError: If script execution fails AsepriteFileNotFoundError: If specified file doesn't exist """ # Validate script size script_size = len(script_content.encode('utf-8')) if script_size > self.config.lua.max_script_size: logger.error(f"Lua script too large", size=script_size, max_size=self.config.lua.max_script_size) raise LuaScriptError( script_content[:100] + "...", f"Script exceeds maximum size of {self.config.lua.max_script_size} bytes" ) logger.debug(f"Executing Lua script", size=script_size, has_file=bool(filename)) # Validate file if provided if filename: try: file_path = validate_file_path(filename, must_exist=True) # Check if path is allowed if not self.config.is_path_allowed(file_path): raise LuaScriptError( script_content[:100] + "...", f"Access to path '{file_path}' is not allowed by security configuration" ) filename = str(file_path) except Exception as e: if isinstance(e, LuaScriptError): raise raise AsepriteFileNotFoundError(filename) # Create a temporary file for the script try: with tempfile.NamedTemporaryFile(suffix='.lua', delete=False, mode='w') as tmp: tmp.write(script_content) script_path = tmp.name except Exception as e: raise LuaScriptError(script_content[:100] + "...", f"Failed to create temp file: {e}") try: args = ["--batch"] if filename: args.append(filename) args.extend(["--script", script_path]) with Timer("execute_lua_script", script_size=len(script_content), file=filename): success, output = self.run_command(args) if not success: logger.error("Lua script execution failed", output=output) raise LuaScriptError(script_content[:100] + "...", output) logger.debug("Lua script executed successfully") return success, output except CommandExecutionError as e: logger.error("Lua script command error", error=str(e)) raise LuaScriptError(script_content[:100] + "...", e.details['stderr']) finally: # Clean up the temporary script file try: os.remove(script_path) except: pass # Ignore cleanup errors # Global instance for backward compatibility _global_command = None def get_command() -> AsepriteCommand: """Get global AsepriteCommand instance.""" global _global_command if _global_command is None: _global_command = AsepriteCommand() return _global_command

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ext-sakamoro/AsepriteMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server