run python script ?

I would love to be able to trigger an action that runs a python script of my choosing, is this impossible ? i only see js and AS

@Pal you might know about this !

you can run anything via the run shell script actions (also from JS).

awesome. already back from vacations ?

okay I just tried and it gives me this error . unfortunately i used gemini for this so im tryly dumbfounded. I imagine I need some sort of environment, which alread has but btt does not ? (alfred relies on brew python )

/bin/bash: line 2: import: command not found

/bin/bash: line 3: import: command not found

/bin/bash: line 4: import: command not found

/bin/bash: line 5: import: command not found

/bin/bash: line 6: import: command not found

/bin/bash: line 7: from: command not found

/bin/bash: -c: line 9: syntax error near unexpected token `('

/bin/bash: -c: line 9: `def take_screenshot():'

Like this?

//if you rename this, make sure to also rename it in the "function to call" field below.
async function someJavaScriptFunction() {
    let python_script_path = '~/mycoolscreenshotscript.py';
	let python_path = '/opt/homebrew/bin/python3'; // or the path to the python executable in your virtual env
	let cfg = { script: `${python_path} ${python_script_path}`, launchPath: '/bin/bash', parameters: '-c' };
    let result = await runShellScript(cfg);
    return(result);
}

how does your shell script look like?
Best put your python in a file on disk.
Depending on what your script does you might need to provide environment variables

#!/usr/bin/env python3

import subprocess
import tempfile
import os
import webbrowser
import json
from urllib.parse import quote

def take_screenshot():
"""Take an interactive screenshot and return the file path"""
temp_dir = tempfile.mkdtemp()
screenshot_path = os.path.join(temp_dir, f"screenshot-{int(import('time').time())}.png")

try:
    # Take interactive screenshot (same as Raycast: screencapture -i)
    result = subprocess.run([
        "/usr/sbin/screencapture", "-i", screenshot_path
    ], capture_output=True, text=True)
    
    # Check if screenshot was taken (file exists and has content)
    if not os.path.exists(screenshot_path) or os.path.getsize(screenshot_path) == 0:
        print("Screenshot cancelled")
        return None
        
    print(f"Screenshot saved to: {screenshot_path}")
    return screenshot_path
    
except Exception as e:
    print(f"Screenshot failed: {e}")
    return None

def upload_to_raycast_server(image_path):
"""Upload image using the same server that Raycast extension uses"""
try:
print("Uploading to Raycast Google Lens server...")

    # Use the exact same API endpoint as Raycast
    api_base_url = "https://google-lens.zeabur.app"
    upload_endpoint = f"{api_base_url}/upload-image"
    
    filename = os.path.basename(image_path)
    
    # Create multipart form data using curl (same as FormData in Node.js)
    result = subprocess.run([
        "curl", "-s", "-X", "POST",
        "-F", f"image=@{image_path};filename={filename};type=image/png",
        upload_endpoint
    ], capture_output=True, text=True)
    
    if result.returncode == 0:
        try:
            response_data = json.loads(result.stdout)
            print(f"Server response: {response_data}")
            
            if response_data.get("filename"):
                # Construct image URL exactly like Raycast does
                image_url = f"{api_base_url}/image/{response_data['filename']}"
                print(f"Image uploaded: {image_url}")
                
                # Open Google Lens with the image URL (same as Raycast)
                lens_url = f"https://lens.google.com/uploadbyurl?url={quote(image_url)}"
                print(f"Opening: {lens_url}")
                
                webbrowser.open(lens_url)
                
                # Show success notification
                subprocess.run([
                    "osascript", "-e", 
                    'display notification "Opening Google Lens..." with title "Alfred Google Lens" sound name "Glass"'
                ])
                
                return True
            else:
                print(f"Upload failed: {response_data.get('message', 'Unknown error')}")
                
        except json.JSONDecodeError:
            print(f"Invalid JSON response: {result.stdout}")
            
    else:
        print(f"Curl failed with code {result.returncode}: {result.stderr}")
        
except Exception as e:
    print(f"Upload to Raycast server failed: {e}")
    
return False

def upload_to_alternative_server(image_path):
"""Fallback: Try alternative image hosting that works with Google Lens"""
try:
print("Trying alternative upload service...")

    # Try Imgur (reliable with Google Lens)
    result = subprocess.run([
        "curl", "-s", "-H", "Authorization: Client-ID 546c25a59c58ad7",
        "-F", f"image=@{image_path}",
        "https://api.imgur.com/3/upload"
    ], capture_output=True, text=True)
    
    if result.returncode == 0 and "data" in result.stdout:
        try:
            response_data = json.loads(result.stdout)
            if response_data.get("success"):
                image_url = response_data["data"]["link"]
                print(f"Imgur upload successful: {image_url}")
                
                # Open Google Lens with Imgur URL
                lens_url = f"https://lens.google.com/uploadbyurl?url={quote(image_url)}"
                webbrowser.open(lens_url)
                
                subprocess.run([
                    "osascript", "-e", 
                    'display notification "Uploaded via Imgur - Opening Google Lens..." with title "Alfred Google Lens" sound name "Glass"'
                ])
                
                return True
        except json.JSONDecodeError:
            pass
            
except Exception as e:
    print(f"Alternative upload failed: {e}")
    
return False

def clipboard_fallback(image_path):
"""Final fallback: Copy to clipboard method"""
try:
print("Using clipboard fallback method...")

    # Copy image to clipboard
    subprocess.run([
        "osascript", "-e",
        f'tell application "System Events" to set the clipboard to (read (POSIX file "{image_path}") as «class PNGf»)'
    ], check=True)
    
    # Open Google Lens
    webbrowser.open("https://lens.google.com/")
    
    # Show instruction notification
    subprocess.run([
        "osascript", "-e", 
        'display notification "Screenshot in clipboard! Click camera icon and paste (⌘V)" with title "Alfred Google Lens" sound name "Glass"'
    ])
    
    return True
    
except Exception as e:
    print(f"Clipboard fallback failed: {e}")
    return False

def main():
print("Alfred Google Lens - Taking screenshot...")

# Take screenshot using the same method as Raycast
screenshot_path = take_screenshot()

if not screenshot_path:
    return

success = False

# Method 1: Use the same server as Raycast extension
if upload_to_raycast_server(screenshot_path):
    success = True
    print("✅ Successfully uploaded using Raycast server")

# Method 2: Fallback to Imgur (also reliable with Google Lens)
elif upload_to_alternative_server(screenshot_path):
    success = True
    print("✅ Successfully uploaded using alternative server")

# Method 3: Final fallback - clipboard method
else:
    if clipboard_fallback(screenshot_path):
        print("✅ Using clipboard method - paste into Google Lens")
    else:
        print("❌ All methods failed")

# Cleanup temp file
try:
    if os.path.exists(screenshot_path):
        os.remove(screenshot_path)
        # Remove temp directory if empty
        temp_dir = os.path.dirname(screenshot_path)
        if os.path.exists(temp_dir) and not os.listdir(temp_dir):
            os.rmdir(temp_dir)
    print("Temp files cleaned up")
except Exception as e:
    print(f"Cleanup warning: {e}")

if name == "main":
main()

@avi are you able to successfully execute the Python script outside of BTT? What exact command do you use to execute it?