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.
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:
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.
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
| Option | Meaning |
|---|---|
-l LANGUAGE | Script language — AppleScript (default) or JavaScript |
-e SCRIPT | Inline script; repeat for multi-line scripts |
-s OPTION | Modify output behaviour: o = print as machine-readable, s = no set syntax, h/e = human-readable / error-on-stderr |
-i | Interactive mode — run a REPL (rarely needed) |
-ss, -so, -sh, -se | Common -s combinations |
--language LANG | Long form of -l |
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:
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.
| Concept | AppleScript | JXA |
|---|---|---|
| Talk to an app | tell application "Finder" to … | Application("Finder").… |
| Get app process | tell application "System Events" | Application("System Events") |
| Variables | set x to 5 | var x = 5; |
| Strings | "hello" | "hello" |
| Lists / Arrays | {1, 2, 3} | [1, 2, 3] |
| Records / Objects | {name:"a", age:30} | {name: "a", age: 30} |
| Run shell | do shell script "ls" | app.doShellScript("ls") (Standard Additions) |
| Standard Additions | implicit (display dialog …) | Application.currentApplication(); app.includeStandardAdditions = true |
| ObjC bridge | limited | ObjC.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.
# 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:
/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.
# 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:
/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.
# 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:
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.
# 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:
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.
# 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:
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.
# 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:
/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.
# 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:
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.
# 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:
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).
# 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:
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.
# 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:
kTCCServiceAppleEvents|com.apple.Terminal
kTCCServiceAppleEvents|com.googlecode.iterm2
Common pitfalls
- 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.
- Quoting hell — every layer (shell → osascript → AppleScript → target app) re-parses quotes. Prefer here-docs (
<<'EOF') over chained-eflags for anything non-trivial. do shell scriptruns without your shell rc — the spawned shell is/bin/sh -c …with a minimal environment. SetPATHinside the script:do shell script "PATH=/usr/local/bin:$PATH && …".- AppleScript dates ≠ Unix epoch —
current datereturns an AppleScript date object, not a number. Convert with(current date) - (date "Thursday, January 1, 1970 at 00:00:00")for the epoch seconds. returnin JXA prints undefined — JXA returns the last expression, not viareturnoutside a function.osascript -l JavaScript -e 'return 1+2'prints nothing; drop thereturn.- App is not scriptable — many GUI apps have no scripting dictionary. Use System Events
keystroke/clickas a fallback, but accessibility-driven automation is fragile across OS upgrades. - JXA exits silently on syntax errors with
-eand no-ss— pass-ss(script-error-on-stderr) so JavaScript parse errors are visible. text returned of (display dialog …)errors on Cancel — the user pressing Cancel raises error -128. Wrap intry … on error number -128 … end tryto swallow it.- Notification not appearing — Focus / Do-Not-Disturb suppresses notifications. Also: the parent app must be approved in System Settings → Notifications.
- Slow startup —
osascriptwarm-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.
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 networksetup — osascript's contribution is the dialog wrapper.
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.
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.
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.
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.
#!/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.
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:
[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.
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:
- [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.
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:
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).
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.
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:
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
osascriptremains 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.
| Issue | Effect | Workaround |
|---|---|---|
tell application "iTunes" alias removed | Long-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 removed | Reading 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 foregrounds | tell 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 beachball | Persistent ~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-rewrite | Compiled references like tell application "Numbers" sometimes silently rewrite to tell application "Numbers Creator Studio" on first edit. | Inspect with osadecompile script.scpt before deployment. |
# 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:
FAIL: /Users/alice/Scripts/old-archiver.scpt
FAIL: /Users/alice/Scripts/legacy-finder-driver.scpt
JXA scripts are not affected by the
errOSADataFormatObsoletewire-format bump — only AppleScript.scptfiles are. If you have a choice between AppleScript and JXA for new code, JXA's.scptformat 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