cheat sheet

osascript

Drive macOS GUI apps, system events, dialogs, notifications, and Finder from the terminal using AppleScript or JavaScript for Automation (JXA).

osascript — Run AppleScript and JXA From the Shell

What it is

osascript is the macOS Open Scripting Architecture command-line front-end — it executes scripts written in AppleScript (the default) or JavaScript for Automation (JXA), the two languages Apple supports for inter-application scripting. It has shipped with every release of OS X since 10.0, lives at /usr/bin/osascript, and is signed by Apple. Reach for osascript whenever you need to drive a GUI app from a shell pipeline, show a native dialog or notification, or read/write state that has no defaults-style CLI exposure. The two competing approaches are defaults/launchctl for low-level system settings and Hammerspoon/Keyboard Maestro for GUI macros; osascript is the right tool when the action is Apple-script-aware — i.e. the target app exposes an AppleScript dictionary.

Install

osascript ships with macOS. Apple has deprecated new language plug-ins (/Library/Components/), but the two built-in languages — AppleScript and JavaScript — are not going away.

bash
which osascript
# /usr/bin/osascript

osascript -l AppleScript -e 'return system version of (system info)'
osascript -l JavaScript  -e 'ObjC.import("Foundation"); \
                              $.NSProcessInfo.processInfo.operatingSystemVersionString.js'

Output:

text
14.5
Version 14.5 (Build 23F79)

Syntax

osascript accepts a script in three ways: inline via -e, from a file by path, or from stdin. It writes the script's result to stdout and any errors to stderr. The exit code is non-zero on a runtime error or syntax failure.

bash
osascript [-l LANGUAGE] -e 'SCRIPT'                # inline, one line
osascript [-l LANGUAGE] -e 'LINE1' -e 'LINE2'# inline, multiple lines
osascript [-l LANGUAGE] PATH/TO/SCRIPT             # script file
osascript [-l LANGUAGE] -                          # read from stdin

Output: (none — exits 0 on success)

Essential options

OptionMeaning
-l LANGUAGEScript language — AppleScript (default) or JavaScript
-e SCRIPTInline script; repeat for multi-line scripts
-s OPTIONModify output behaviour: o = print as machine-readable, s = no set syntax, h/e = human-readable / error-on-stderr
-iInteractive mode — run a REPL (rarely needed)
-ss, -so, -sh, -seCommon -s combinations
--language LANGLong form of -l
bash
osascript -e 'return 1 + 2'                              # default: AppleScript
osascript -l JavaScript -e '1 + 2'                       # JXA
osascript -s s -e 'return current date'                  # machine-readable
osascript -s s -e 'return {1, 2, 3}'                     # list as braces

Output:

text
3
3
date "Sunday, May 25, 2026 at 9:14:32 AM"
{1, 2, 3}

The two languages at a glance

The same script logic looks very different in AppleScript and JXA. AppleScript reads like English and is the historical default; JXA is JavaScript with a special Application(...) proxy for talking to apps. Both call into the same underlying Apple Events.

ConceptAppleScriptJXA
Talk to an apptell application "Finder" to …Application("Finder").…
Get app processtell application "System Events"Application("System Events")
Variablesset x to 5var x = 5;
Strings"hello""hello"
Lists / Arrays{1, 2, 3}[1, 2, 3]
Records / Objects{name:"a", age:30}{name: "a", age: 30}
Run shelldo shell script "ls"app.doShellScript("ls") (Standard Additions)
Standard Additionsimplicit (display dialog …)Application.currentApplication(); app.includeStandardAdditions = true
ObjC bridgelimitedObjC.import("Foundation"); $.NSDate.date
Comments-- or (* … *)// or /* … */

A practical rule of thumb: pick AppleScript for short, English-readable automations and for scripts the wider Mac scripting community will recognise; pick JXA when the script needs real data structures, regex, JSON, or Foundation APIs.

AppleScript primer

AppleScript is verb-noun, English-styled, and intentionally readable. The core unit is the tell block — every statement inside tell application "X" is dispatched to that app's scripting dictionary. The dictionary is the canonical reference for what verbs and properties an app exposes; open it from Script Editor → File → Open Dictionary.

bash
# Get the front Finder window's path
osascript -e 'tell application "Finder" to return POSIX path of (target of front window as alias)'

# Greet the current user
osascript -e 'set u to short user name of (system info)' \
          -e 'return "Hello, " & u'

# Get the system version
osascript -e 'return system version of (system info)'

# Multi-line — control flow
osascript -e 'set n to 0' \
          -e 'repeat with i from 1 to 5' \
          -e '  set n to n + i' \
          -e 'end repeat' \
          -e 'return n'

Output:

text
/Users/alice/Documents/
Hello, alice
14.5
15

JavaScript for Automation (JXA) primer

JXA was introduced in Yosemite (10.10). It exposes the same Apple Events bridge as AppleScript, but lets you write modern JavaScript — arrays, regex, JSON, Promises (sort of). The proxy Application("name") is the equivalent of tell application "name". Standard Additions (dialogs, file pickers, shell-out) must be enabled explicitly on currentApplication.

bash
# Equivalent of the AppleScript above
osascript -l JavaScript -e '
const Finder = Application("Finder");
const path = Finder.windows[0].target().url();
path.replace(/^file:\/\//, "");
'

# Get the system version
osascript -l JavaScript -e '
ObjC.import("Foundation");
$.NSProcessInfo.processInfo.operatingSystemVersionString.js;
'

# JSON-handling: parse stdin, return a key
osascript -l JavaScript -e '
function run(argv) {
  return JSON.parse(argv[0]).name.toUpperCase();
}
' '{"name":"alice"}'

Output:

text
/Users/alice/Documents/
Version 14.5 (Build 23F79)
ALICE

Dialogs and notifications

The Standard Additions library bundles a handful of cross-app helpers — alerts, dialogs, file pickers, and notifications. They are the fastest way to get user input or surface a result from a shell script without writing a full GUI.

bash
# Native banner notification
osascript -e 'display notification "Build complete" with title "Make" subtitle "myhost.local" sound name "Glass"'

# Modal dialog with an OK button
osascript -e 'display dialog "Proceed with deployment?" buttons {"Cancel","Deploy"} default button "Deploy"'

# Input prompt — capture the typed answer
result=$(osascript -e 'text returned of (display dialog "Project name?" default answer "")')
echo "got: $result"

# Choose from a list
choice=$(osascript -e 'choose from list {"main","develop","feature/x"} with prompt "Branch?"')
echo "chose: $choice"

# Alert (icon + buttons + title)
osascript -e 'display alert "Heads up" message "Disk usage above 90%" as warning'

Output:

text
got: jockey
chose: feature/x

Bridging to the shell with do shell script

do shell script runs an arbitrary shell command from inside AppleScript and returns its stdout as a string. It is the most common bridge back into the Unix side of macOS — useful when you need elevated privileges (with administrator privileges) or want to combine AppleScript's UI features with normal CLI tools.

bash
# Capture shell output into AppleScript
osascript -e 'do shell script "date +%Y-%m-%d"'

# Combine: ask the user, then act with the shell
osascript -e 'set name to text returned of (display dialog "Filename?" default answer "")' \
          -e 'do shell script "touch ~/Desktop/" & quoted form of name'

# Privilege escalation (shows the GUI auth prompt)
osascript -e 'do shell script "sudo -n true" with administrator privileges'

# JXA equivalent
osascript -l JavaScript -e '
const app = Application.currentApplication();
app.includeStandardAdditions = true;
app.doShellScript("date +%Y-%m-%d");
'

Output:

text
2026-05-25

Driving apps via System Events

System Events is a built-in app that exposes the running process list, accessibility (UI) elements, and a generic file/folder API. Talking to it lets you script apps that don't have their own AppleScript dictionary, by sending raw keystrokes, clicks, and menu selections.

bash
# Send Cmd-S to the frontmost app
osascript -e 'tell application "System Events" to keystroke "s" using {command down}'

# Open the Spotlight bar with Cmd-Space
osascript -e 'tell application "System Events" to keystroke " " using {command down}'

# List every running process (visible apps)
osascript -e 'tell application "System Events" to get name of every process where background only is false'

# Get the menu bar items of the frontmost app
osascript -e '
tell application "System Events"
  tell process (name of (first application process whose frontmost is true))
    get name of every menu bar item of menu bar 1
  end tell
end tell
'

# Quit an app safely
osascript -e 'tell application "Slack" to quit'

Output:

text
Finder, Safari, Mail, Slack, Terminal, Code
Apple, Safari, File, Edit, View, History, Bookmarks, Develop, Window, Help

File and Finder operations

The Finder dictionary exposes the file/folder model the GUI sees, including selection state and window targets. AppleScript's POSIX-path round-tripping is awkward; use the POSIX path of and POSIX file conversions explicitly.

bash
# Reveal a file in Finder
osascript -e 'tell application "Finder" to reveal POSIX file "/Users/alice/Documents/report.pdf"' \
          -e 'tell application "Finder" to activate'

# Get the path of every currently-selected item in Finder
osascript -e 'tell application "Finder" to return POSIX path of (selection as alias list)'

# Move a file to the Trash
osascript -e 'tell application "Finder" to delete POSIX file "/Users/alice/Desktop/old.txt"'

# Empty the Trash
osascript -e 'tell application "Finder" to empty trash'

# Eject all removable disks
osascript -e 'tell application "Finder" to eject (every disk whose ejectable is true)'

Output:

text
/Users/alice/Documents/report.pdf, /Users/alice/Documents/draft.md

Clipboard and pasteboard

The Standard Additions provide a tiny clipboard API. the clipboard returns the current contents; set the clipboard to … writes. It is convenient inline when you don't want to shell out to pbcopy/pbpaste.

bash
# Read the clipboard
osascript -e 'return the clipboard'

# Write to the clipboard
osascript -e 'set the clipboard to "hello from osascript"'

# Read a specific representation (e.g. text vs styled text)
osascript -e 'return the clipboard as text'

# JXA equivalent
osascript -l JavaScript -e '
const app = Application.currentApplication();
app.includeStandardAdditions = true;
app.theClipboard();
'

Output:

text
hello from osascript

Saving scripts as .scpt or .app

For anything longer than a few -e flags, write the script to a file. Two formats are common: .applescript (plain text) and .scpt (compiled binary). osacompile is the companion compiler — it converts plain text into compiled scripts or applets.

bash
# Plain-text script
cat > greet.applescript <<'EOF'
on run argv
  set who to "world"
  if (count of argv) > 0 then set who to item 1 of argv
  return "Hello, " & who
end run
EOF
osascript greet.applescript Alice

# Compile to .scpt
osacompile -o greet.scpt greet.applescript
osascript greet.scpt Alice

# Compile to a double-clickable .app bundle
osacompile -o Greet.app greet.applescript

# Run a stdin pipeline
echo 'return "hi"' | osascript -

Output:

text
Hello, Alice
Hello, Alice
hi

Passing arguments

Both languages accept positional arguments from the shell, but the syntax differs. AppleScript uses an on run argv handler; JXA uses function run(argv).

bash
# AppleScript
osascript -e 'on run argv
                return "first: " & item 1 of argv
              end run' \
          alpha beta gamma

# JXA
osascript -l JavaScript -e '
function run(argv) {
  return JSON.stringify(argv);
}
' --foo bar baz

Output:

text
first: alpha
["--foo","bar","baz"]

TCC / accessibility prompts

The first time a script tries to drive another app via System Events, macOS shows a Privacy & Security prompt: "Terminal would like to control X". Until the user accepts, the call returns error -1743 (Not authorized to send Apple events). The grant is per parent process (Terminal, iTerm2, VS Code's integrated terminal each count separately). Grants are stored in the TCC database and persist across reboots.

bash
# Triggers a TCC prompt the first time it runs from a new parent process
osascript -e 'tell application "System Events" to get name of first process'

# Inspect (read-only) what your shell session has been granted
sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db \
  "SELECT service, client FROM access WHERE service='kTCCServiceAppleEvents'"

Output:

text
kTCCServiceAppleEvents|com.apple.Terminal
kTCCServiceAppleEvents|com.googlecode.iterm2

Common pitfalls

  1. Error -1743 — "Not authorized to send Apple events" — first-run TCC prompt was dismissed. Open System Settings → Privacy & Security → Automation and re-enable the toggle for your terminal app.
  2. Quoting hell — every layer (shell → osascript → AppleScript → target app) re-parses quotes. Prefer here-docs (<<'EOF') over chained -e flags for anything non-trivial.
  3. do shell script runs without your shell rc — the spawned shell is /bin/sh -c … with a minimal environment. Set PATH inside the script: do shell script "PATH=/usr/local/bin:$PATH && …".
  4. AppleScript dates ≠ Unix epochcurrent date returns an AppleScript date object, not a number. Convert with (current date) - (date "Thursday, January 1, 1970 at 00:00:00") for the epoch seconds.
  5. return in JXA prints undefined — JXA returns the last expression, not via return outside a function. osascript -l JavaScript -e 'return 1+2' prints nothing; drop the return.
  6. App is not scriptable — many GUI apps have no scripting dictionary. Use System Events keystroke / click as a fallback, but accessibility-driven automation is fragile across OS upgrades.
  7. JXA exits silently on syntax errors with -e and no -ss — pass -ss (script-error-on-stderr) so JavaScript parse errors are visible.
  8. text returned of (display dialog …) errors on Cancel — the user pressing Cancel raises error -128. Wrap in try … on error number -128 … end try to swallow it.
  9. Notification not appearing — Focus / Do-Not-Disturb suppresses notifications. Also: the parent app must be approved in System Settings → Notifications.
  10. Slow startuposascript warm-start is ~80 ms; for tight loops, write a single long script with internal iteration rather than shelling out per item.

Real-world recipes

Screenshot, timestamp, and reveal in Finder

A one-liner that captures the full screen, writes it to ~/Desktop with a timestamp filename, and pops Finder open with the file selected.

bash
osascript <<'EOF'
set ts to do shell script "date +%Y-%m-%d-%H%M%S"
set destPath to (POSIX path of (path to desktop)) & "shot-" & ts & ".png"
do shell script "screencapture -x " & quoted form of destPath
tell application "Finder"
  reveal POSIX file destPath
  activate
end tell
EOF

Output: (none — Finder opens with the new file selected)

Toggle Wi-Fi

Read the current Wi-Fi power state and flip it. Uses do shell script to call networksetuposascript's contribution is the dialog wrapper.

bash
osascript <<'EOF'
set state to do shell script "networksetup -getairportpower en0 | awk '{print $NF}'"
if state is "On" then
  do shell script "networksetup -setairportpower en0 off"
  display notification "Wi-Fi disabled" with title "Network"
else
  do shell script "networksetup -setairportpower en0 on"
  display notification "Wi-Fi enabled" with title "Network"
end if
EOF

Output: (none — Wi-Fi toggled; banner shown)

Open the current Terminal directory in Finder

A short JXA snippet that asks Terminal for the current tab's working directory and reveals it in Finder.

bash
osascript -l JavaScript <<'EOF'
const Terminal = Application("Terminal");
const path = Terminal.windows[0].selectedTab.tty().replace(/^.*\/dev\//, "/dev/");
const Finder = Application("Finder");
Finder.activate();
Finder.open(Path("/Users/alice/code/jockey"));
EOF

Output: (none — Finder opens the path)

Pause Music and mute system volume on Zoom call start

A focus-mode helper: pause Music, mute the system, drop a notification. Trigger from a launchd agent or a hotkey.

bash
osascript <<'EOF'
tell application "Music"
  if it is running and player state is playing then pause
end tell
set volume with output muted
display notification "Music paused, system muted" with title "Focus" sound name "Submarine"
EOF

Output: (none — actions performed; notification shown)

Set the desktop wallpaper

Drives System Events to swap the desktop picture. Useful in onboarding scripts that bake a corporate wallpaper into a new Mac.

bash
osascript -e 'tell application "System Events" to tell every desktop to set picture to "/Users/alice/Pictures/wallpaper.jpg"'

Output: (none — wallpaper changed)

Post a system notification from anywhere

A reusable helper. Drop in ~/bin/notify and chmod +x.

bash
#!/usr/bin/env bash
# usage: notify "title" "message" [sound]
title=${1:-Notice}
msg=${2:-}
sound=${3:-Glass}
osascript -e "display notification \"$msg\" with title \"$title\" sound name \"$sound\""

Output: (none — banner shown)

Get the URL of the front Safari tab

A frequently-useful one-liner for piping a Safari URL into the clipboard, a markdown link, or a chat message.

bash
osascript -e 'tell application "Safari" to return URL of front document'

# Or as a markdown link
osascript <<'EOF'
tell application "Safari"
  set u to URL of front document
  set t to name of front document
  return "[" & t & "](" & u & ")"
end tell
EOF

Output:

text
[GitHub: jockey](https://github.com/alicedev/jockey)

Iterate Chrome tabs

Walk every window and tab of Google Chrome (or another Chromium app) and print title + URL — useful for archiving a session into a markdown file.

bash
osascript -l JavaScript <<'EOF'
const Chrome = Application("Google Chrome");
const out = [];
Chrome.windows().forEach((w, wi) => {
  w.tabs().forEach((t, ti) => {
    out.push(`- [${t.title()}](${t.url()})`);
  });
});
out.join("\n");
EOF

Output:

text
- [GitHub: jockey](https://github.com/alicedev/jockey)
- [Apple Developer Documentation](https://developer.apple.com/documentation/)
- [Hacker News](https://news.ycombinator.com/)

Pop an input prompt from a CI / shell script

Block a shell script until the user supplies a value, with a cancellable dialog. Avoids the awkwardness of read in scripts running from launchd.

bash
answer=$(osascript <<'EOF'
try
  set r to text returned of (display dialog "API token?" default answer "" with hidden answer)
on error number -128
  return ""
end try
return r
EOF
)

if [ -z "$answer" ]; then
  echo "cancelled" >&2; exit 1
fi
echo "got token of $(echo -n "$answer" | wc -c | tr -d ' ') chars"

Output:

text
got token of 40 chars

Move the Dock to the left and apply

A pairing of defaults (write the pref) with osascript (restart the Dock through the friendlier System Events approach, which does not flash the screen the way killall Dock does).

bash
defaults write com.apple.dock orientation -string left
osascript -e 'tell application "System Events" to tell process "Dock" to set position of windows to {0, 22}' || true
killall Dock

Output: (none — Dock now on the left)

Drive a notification with reply buttons via terminal-notifier fallback

display notification does not support reply buttons. When you need an actionable notification, fall through to the third-party terminal-notifier (brew install terminal-notifier); for everything else, stick with osascript.

bash
if command -v terminal-notifier >/dev/null; then
  terminal-notifier -title "Build" -message "Deploy?" -actions "Yes,No"
else
  osascript -e 'display dialog "Deploy?" buttons {"No","Yes"} default button "Yes"'
fi

Output:

text
button returned:Yes

The single most useful command when learning AppleScript is osascript -e 'tell application "X" to get properties of front window' — it dumps every property the app exposes, which is the fastest way to discover the syntax for the next step. Try it against Safari, Finder, Mail, and Music.

Apple has not invested in new automation languages since JXA in 2014, but neither AppleScript nor JXA is going away — both are still officially supported and used internally at Apple. The community is gradually shifting to Swift-based tools (e.g. SwiftAutomation) and CLI-only utilities, but osascript remains the lowest-friction way to script GUI apps from a shell.

macOS Tahoe 26 — what changed

Tahoe 26 (Liquid Glass) shipped several behavioural and compatibility regressions that affect every osascript user, even though the binary itself is unchanged. Audit any script you maintain before upgrading.

IssueEffectWorkaround
tell application "iTunes" alias removedLong-standing back-compat shim is gone; only "Music" works. "System Preferences", "iCal", "Address Book" aliases still resolve.Rewrite to tell application "Music".
Music current track event removedReading current track from Music.app raises errAEEventNotHandled (-10000).Pull track info via name of current track / artist of current track directly, or fall back to MediaRemote private APIs.
Script Editor errOSADataFormatObsolete (-1758)macOS 26.4 Script Editor cannot open many older .scpt files; osascript running the same files reports errOSADataFormatObsolete.Open in 26.3 (or an older Mac), copy text into a new document, save as a fresh .scpt. The wire format changed.
activate from CLI no longer foregroundstell application "X" to activate launches the app but does not bring its windows to the front. Worked through Sequoia 15, regressed in Tahoe 26.Add tell application "System Events" to set frontmost of process "X" to true after activate.
display dialog spinning beachballPersistent ~10–20 s hang the first time a script shows a dialog after upgrade. Recompiling and re-saving the script clears the slowdown.Re-save every .scpt after upgrade: open in Script Editor, save in place.
App-name auto-rewriteCompiled references like tell application "Numbers" sometimes silently rewrite to tell application "Numbers Creator Studio" on first edit.Inspect with osadecompile script.scpt before deployment.
bash
# Quickly identify scripts that will not run under Tahoe 26.4+
for f in ~/Scripts/*.scpt; do
  osascript "$f" </dev/null >/dev/null 2>&1 \
    || echo "FAIL: $f"
done

# Bulk re-save every .scpt to refresh the wire format
for f in ~/Scripts/*.scpt; do
  osadecompile "$f" 2>/dev/null > "/tmp/$(basename "$f").applescript" \
    && osacompile -o "$f" "/tmp/$(basename "$f").applescript"
done

Output:

text
FAIL: /Users/alice/Scripts/old-archiver.scpt
FAIL: /Users/alice/Scripts/legacy-finder-driver.scpt

JXA scripts are not affected by the errOSADataFormatObsolete wire-format bump — only AppleScript .scpt files are. If you have a choice between AppleScript and JXA for new code, JXA's .scpt format has been more stable across the recent macOS upgrades.

Sources

Scripting Changes (or lack thereof) in macOS Tahoe — MacScripter AppleScript and macOS Tahoe 26 — MacScripter macOS 26.4's Script Editor Won't Open Some Older AppleScripts — TidBITS macOS Tahoe 26 Release Notes — Apple Developer Slow AppleScripts under Tahoe — Apple Community