Script Development Guide

This document is for users who already know some shell / python / applescript—it covers what interpreters menu scripts can use, how arguments are passed, what the environment looks like, and how to debug. New to script menus? Start with Script Menus (Basics).


1. Choosing an Interpreter

iRightMenu Pro recognizes the following script type aliases—write the name in the “Script type” field, the matching interpreter is invoked:

Script type you write Actual interpreter
bash / sh / shell /bin/bash
zsh /bin/zsh
python / python3 /usr/bin/python3
applescript /usr/bin/osascript
javascript /usr/bin/osascript -l JavaScript (JXA)
ruby /usr/bin/ruby
perl /usr/bin/perl
swift /usr/bin/swift
php /usr/bin/php

Any other name (e.g. lua, node, go, rust…) goes through /usr/bin/env <name>—as long as the interpreter is in PATH, it works.

You can also write a full path as the script type, for example:

  • /opt/homebrew/bin/python3 (Homebrew Python)
  • /Users/you/.pyenv/shims/python (pyenv)
  • /opt/homebrew/bin/node

A full path is most reliable—doesn’t depend on PATH precedence.


2. Shell Scripts

Most common. Set script type to bash / sh / zsh. iRightMenu adds the shebang automatically; you don’t need to write #!/bin/bash.

Arguments:

  • $1, $2, $3… = absolute paths of selected files
  • "$@" = all selected items (recommended idiom)
  • $# = number of items selected
echo "Selected $# items"
for f in "$@"; do
    if [ -d "$f" ]; then
        echo "[Folder] $f"
    else
        echo "[File]   $f"
    fi
done

Common pitfalls:

  • Paths with spaces—always wrap variables in double quotes, write "$f" not $f
  • Use set -e to make the script exit on first failure (unless you want fault tolerance)
  • The script runs as a non-interactive shell—it does NOT read aliases / functions from ~/.zshrc

3. Python Scripts

Set script type to python or python3, both map to /usr/bin/python3 (the system Python 3 shipped with macOS).

Arguments: sys.argv[1:] is the list of selected paths (argv[0] is the temp script path itself, skip it).

import sys

for path in sys.argv[1:]:
    print(f"Processing: {path}")

Want Homebrew / pyenv / conda Python?

The default /usr/bin/python3 lacks third-party libs (requests, numpy, etc.). Two options:

  • Use a full path as the script type: /opt/homebrew/bin/python3 or /Users/you/.pyenv/shims/python (recommended)
  • Use /usr/bin/env python3 as the script type: lets the first matching python3 in PATH win—iRightMenu’s default PATH already includes Homebrew

4. AppleScript / JavaScript (osascript)

Script types are applescript and javascript respectively. Both go through /usr/bin/osascript (JS adds -l JavaScript). No automatic shebang.

Important difference: osascript doesn’t auto-expose $1 $2 like bash does. To get selected file paths, use a dedicated entry point:

AppleScript: on run argv

on run argv
    repeat with filePath in argv
        log "Processing: " & filePath
    end repeat
end run

Standard template for controlling another app:

on run argv
    set firstFile to item 1 of argv
    tell application "BBEdit"
        activate
        open POSIX file firstFile
    end tell
end run

JavaScript (JXA): function run(argv)

function run(argv) {
    for (let path of argv) {
        console.log("Processing: " + path);
    }
}

Note console.log writes to stderr; for the result dialog, use return value.


5. Selected Files / Arguments

Basics

  • Arguments = absolute paths of selected files
  • Files and folders are passed identically; the script doesn’t distinguish
  • Working directory = the Finder’s current directory (the window where the menu was triggered)

Paths with Spaces

macOS user directories have many spaces (Application Support / Group Containers / non-ASCII names, etc.). Always wrap variables in double quotes:

# ❌ Spaces will split into multiple args
cp $1 /tmp/

# ✅ Correct
cp "$1" /tmp/

Status Bar Menus: No Arguments

A script attached to the status bar menu has no selected files$@ is empty, $# is 0. Use that as a “triggered from status bar” check:

if [ $# -eq 0 ]; then
    # Status bar trigger—typically a "global" action
    open -a "Calculator"
fi

6. Environment Variables and PATH

Default PATH

Scripts get a PATH already augmented with common Homebrew etc. paths:

/usr/local/bin
/opt/homebrew/bin
/opt/homebrew/sbin
/usr/local/opt/ruby/bin
/opt/local/bin
$HOME/.local/bin

This means Homebrew commands (brew, code, gh, fzf, etc.) just work.

Does NOT Inherit ~/.zshrc Customizations

Scripts run as non-interactive shells—~/.zshrc / ~/.bash_profile are not sourced. So:

# In ~/.zshrc:
# alias ll='ls -lh'
# export MY_TOOL_HOME="/Users/h4ck/tools"

# In the script:
ll              # ❌ command not found
echo $MY_TOOL_HOME    # ❌ empty

Workarounds (in order of preference):

  1. Use full commands directly (write ls -lh instead of ll)
  2. Add a “Custom PATH” in the Inspector for extra dirs
  3. Preferences → Advanced → Shell Environment: KEY=VALUE lines (one per line), inherited by all scripts

Debug: See What the Script Actually Gets

Don’t know what the environment looks like? Run this with “Feedback type” set to “Result dialog”:

echo "=== PATH ==="
echo "$PATH" | tr ':' '\n'
echo "=== All env ==="
printenv | sort
echo "=== CWD ==="
pwd
echo "=== Args ==="
echo "$#: $@"

7. Output and Feedback

“Feedback type” decides what the user sees on success:

Feedback Behavior
None Silent—no feedback on success
Notification System notification “Execution completed”—does NOT show stdout
Result dialog Pops up a dialog with stdout + stderr merged (truncated if too long)

Forced Feedback on Failure

Non-zero exit code (exit 1 / failed command / syntax error) always shows an error dialog (with a “View log” button), regardless of feedback type. Safety net—no silent failures.

Choosing Feedback Type

  • None: bulk operations that don’t need to disturb the user (e.g. batch chmod)
  • Notification: success has meaning but no detail needed (e.g. “copied N paths”)
  • Result dialog: user needs to see the actual output (du -sh showing sizes, md5 showing hashes)

Accessing Clipboard / Controlling Other Apps from Scripts

Scripts can use pbcopy / pbpaste, osascript, open -a and other commands needing a “user login session” normally—pasteboard writes take effect immediately and are picked up correctly by clipboard managers (Paste / Maccy etc.).

# Copy paths to clipboard in shell-quoted format (paste-ready into Terminal)
printf '%q ' "$@" | pbcopy
osascript -e "display notification \"Copied $# paths\" with title \"iRightMenu\""

8. File Match Rules

Match rules decide “on which selected items the menu appears”. Misconfiguration causes the common “right-click and the menu doesn’t show” bug.

File Match Type

Type Rule
None No extension filter; all files match
Extension Only files whose extension is in the list (e.g. ["py", "js"]); case-insensitive
Regex Regex match against the filename only (no path); case-sensitive

Folder Match Type

Only None / Regex (no “Extension”—but .app is technically extension + folder; use regex \.app$).

Executable Only

Check “Executable only” to make the menu appear only when the file has +x permission. This is a permission bit check—not a binary type check; chmod +x somefile.txt qualifies too.

Count Limits

  • Min files / Max files default to 0 = unlimited
  • Set 1 / 1 to mean “show only when exactly one file is selected”
  • Type filter applies first, then count check

File vs Folder Combinations

Show on files Show on folders Behavior
Show only when selection is all files
Show only when selection is all folders
Show when files / folders / mixed
Generic menu: matches all selected items, AND appears on Finder empty-area right-click

The last row has a special semantic—a menu with neither restriction also appears in the “right-click empty area” context menu.


9. Admin-Privileged Scripts

Set “Execution privilege” to root, the script runs with admin privileges. First-time use prompts a system authorization dialog; subsequent runs don’t.

Limitations (Important)

  • Cannot show dialogs—admin scripts have no GUI context
  • Cannot control other apps (osascript / open -a both fail)
  • Cannot write to clipboard (the pasteboard pbcopy reaches in admin session is not the one you see)
  • No access to user login state—your ssh-agent / keychain unlock state is unavailable
  • No timeout mechanism—if the script hangs, it hangs forever

Suitable Use Cases

  • Modifying system config files (/etc/hosts, /Library/... etc.)
  • Operating on system directories outside SIP
  • Running admin-required diagnostics (tcpdump, dtrace)

Unsuitable Use Cases

  • Any GUI operation (dialog / app control / clipboard)
  • User-level config (git config --global / defaults reading user prefs)
  • Anything needing “see the user’s current screen”

If your script needs both admin and GUI, split into two menus—the admin one does system work, the regular one does UI feedback.


10. Debugging and Logs

Per-Script Log File

After each execution, metadata + output is written to a log file: iRightMenu Pro → Preferences → Advanced → “Open log folder”, then Scripts/<menu-title>.log is the full record.

Each entry includes:

  • Timestamp
  • Script type
  • Exit code
  • Working directory
  • Selected files list
  • stdout + stderr output

Three-Step Debug Method

  1. Run in Terminal first—execute manually in Terminal to verify logic
  2. Paste into the menu—copy the working code into a script menu, set feedback to “Result dialog”, run again
  3. Check logs—if still wrong, see Scripts/<menu-title>.log for full output + env vars + exit code

Common Symptoms → Where to Look

Symptom Common cause
Menu doesn’t show on right-click Match rules wrong (extension / count / file vs folder)
command not found Default PATH doesn’t include the directory—use Custom PATH or absolute path
Path parsing errors Variable not in double quotes; spaces split the path
Empty output / no response “Feedback type” set to “None”—temporarily set to “Result dialog”
AppleScript / JS can’t get args Missing on run argv / function run(argv) entry (see §4)

11. Advanced Recipes

These are advanced examples for this guide. For simple recipes (VSCode open / total size / md5 etc.), see the basics page.

Copy file:// URLs (URL-Encoded)

Convert paths to file:// URLs—handy for browser or Markdown:

import sys
import urllib.parse
import subprocess

urls = ["file://" + urllib.parse.quote(p) for p in sys.argv[1:]]
text = "\n".join(urls)
subprocess.run(["pbcopy"], input=text, text=True)
print(f"Copied {len(urls)} file:// URLs")

AppleScript: Open Multiple Files in BBEdit

on run argv
    tell application "BBEdit"
        activate
        repeat with p in argv
            open POSIX file p
        end repeat
    end tell
end run

Python: Batch Add Date Prefix

import sys
import os
import datetime

prefix = datetime.date.today().isoformat() + "_"
for path in sys.argv[1:]:
    dir_, name = os.path.split(path)
    if name.startswith(prefix):
        continue   # already prefixed
    os.rename(path, os.path.join(dir_, prefix + name))
print(f"Renamed {len(sys.argv) - 1} files")

Status Bar Quick Action: Open Terminal at Finder’s Folder

Place this on a status-bar menu (no file args):

tell application "Terminal"
    activate
    do script "cd " & quoted form of (system attribute "PWD")
end tell

12. When to Upgrade to a Lua Plugin

When scripts hit these “thresholds”, consider writing a Lua plugin:

  • Need a UI form: let user pick a certificate / select options / enter config—scripts can’t, plugins have a complete form API
  • Need persistent state: remember settings across menu invocations—scripts can only write files, plugins have keychain / sqlite APIs
  • Cross-menu reuse: same logic triggered by multiple menus—scripts require copy-paste, plugins can export multiple menu items from one codebase
  • Distribution: scripts are just text to copy-paste, plugins can be uploaded to the plugin store for one-click install

Further reading: Plugin Development GuideAPI ReferenceExample Plugins.

Developer Documentation
User Guide
Getting Started Script Menus FAQ
Script Development
Development Guide
Plugin Development
Quick Start Development Guide Example Plugins
API Reference
Overview API Query Plugin Info Logging Finder Context Plugin Settings Internationalization
UI & Interaction
Dialog Progress Notification Chooser WebView Status Bar Dock
Files & Paths
File Operations Path Utilities Finder Actions Trash Extended Attributes Metadata File Watcher
Data Formats
JSON Plist CSV XML PDF Image
Text & Encoding
String Regex Date & Time Color Crypto
System
Shell Commands Process Application System Info AppleScript Shortcuts
System Info
Network Power/Battery Screen/Appearance Audio Bluetooth Location
Network
HTTP WebSocket URL
Input & Clipboard
Keyboard Mouse Hotkey Clipboard Window
Storage
SQLite Keychain UserDefaults
Media
OCR QR Code
Utilities
Archive UTI Share Timer Wake Lock Thread