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?