claude/skills/mac-use/SKILL.md
macOS automation using AppleScript and JavaScript for Automation (JXA). Use when automating macOS apps, controlling system features, scripting applications like Music, Notes, Finder, Safari, or any scriptable app. Use when the user asks about currently opened screens, UI elements, or visible content in any macOS app.
npx skillsauth add nounder/dotfiles mac-useInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use JavaScript for Automation (JXA) via osascript -l JavaScript for macOS automation. JXA is preferred over AppleScript for its familiar syntax and better data handling.
sdef /Applications/AppName.app
sdef /System/Applications/Music.app
osascript -l JavaScript -e 'Application("AppName").properties()'
osascript -l JavaScript -e '
const app = Application("AppName")
app.includeStandardAdditions = true
// ... operations
'
osascript -l JavaScript -e '
const finder = Application("Finder")
const selection = finder.selection()
selection.map(f => f.name())
'
// Read file
const app = Application.currentApplication()
app.includeStandardAdditions = true
app.read(Path("/path/to/file"))
// Write file
app.write("content", {to: Path("/path/to/file")})
// Open file for access
app.openForAccess(Path("/path"))
osascript -l JavaScript -e '
const finder = Application("Finder")
// Get selected files
const selection = finder.selection()
selection.map(f => ({name: f.name(), path: f.url()}))
// Create folder
finder.make({new: "folder", at: finder.desktop, withProperties: {name: "New Folder"}})
// Move file
finder.move(finder.files["test.txt"], {to: finder.folders["Documents"]})
'
osascript -l JavaScript -e '
const music = Application("Music")
// Search library
const lib = music.playlists.byName("Library")
const results = lib.search({for: "love"})
results.map(t => ({
name: t.name(),
artist: t.artist(),
album: t.album(),
duration: t.duration()
}))
// Playback control
music.play()
music.pause()
music.nextTrack()
music.previousTrack()
// Current track
const track = music.currentTrack
({name: track.name(), artist: track.artist()})
'
Use System Events to inspect the current UI state and visible content:
osascript -l JavaScript -e '
const se = Application("System Events");
const music = se.processes["Music"];
// Get main content title (artist/album/playlist name being viewed)
const mainContent = music.windows[0].splitterGroups[0].scrollAreas[1].lists[0].lists[0].uiElements[0].staticTexts[0];
mainContent.value();
'
osascript -l JavaScript -e '
const se = Application("System Events");
const music = se.processes["Music"];
// Get Singles & EPs list (list index 4 in main content)
const singlesEPsList = music.windows[0].splitterGroups[0].scrollAreas[1].lists[0].lists[4];
const groups = singlesEPsList.groups();
const albums = [];
for (const group of groups.slice(1)) {
try {
const btns = group.buttons();
if (btns.length > 0) {
const name = btns[0].name();
if (name && name !== "1") {
albums.push(name);
}
}
} catch (e) {}
}
albums.join("\n");
'
osascript -l JavaScript -e '
const se = Application("System Events");
const music = se.processes["Music"];
music.windows[0].entireContents();
'
osascript -l JavaScript -e '
const music = Application("Music")
// Search for artist
const lib = music.playlists.byName("Library")
const results = lib.search({for: "Lab'\''s Cloud"})
// Get unique artists from results
const artists = [...new Set(results.map(t => t.artist()))]
// Find the exact artist match
const targetArtist = artists.find(a => a === "Lab'\''s Cloud")
if (targetArtist) {
// Filter tracks by the selected artist
const artistTracks = results.filter(t => t.artist() === targetArtist)
if (artistTracks.length > 0) {
// Play first track and queue the rest
music.play(artistTracks[0])
// Queue remaining tracks
for (let i = 1; i < artistTracks.length; i++) {
music.playPlaylist(lib, false)
}
"Now playing " + targetArtist + " - queued " + artistTracks.length + " tracks"
}
} else {
"Artist not found"
}
'
osascript -l JavaScript -e '
const notes = Application("Notes")
// Create note
notes.make({new: "note", withProperties: {body: "Hello World"}})
// List folders
notes.folders().map(f => f.name())
// Create note in specific folder
const folder = notes.folders.byName("Notes")
notes.make({new: "note", at: folder, withProperties: {name: "Title", body: "Content"}})
'
osascript -l JavaScript -e '
const safari = Application("Safari")
// Get current URL
safari.windows[0].currentTab.url()
// Get all tab URLs
safari.windows[0].tabs().map(t => ({name: t.name(), url: t.url()}))
// Open URL
safari.openLocation("https://example.com")
// Find existing tab or open new one
const Safari = Application("Safari")
Safari.activate()
let foundHackerNews = false
const frontmostWindow = Safari.windows[0]
// Iterate over all tabs in the frontmost window
for (let i = 0; i < frontmostWindow.tabs.length; i++) {
const tab = frontmostWindow.tabs[i]
const tabUrl = tab.url()
if (tabUrl && tabUrl.includes("news.ycombinator.com")) {
frontmostWindow.currentTab = tab
foundHackerNews = true
break
}
}
// If Hacker News was not found, open it in a new tab
if (!foundHackerNews) {
const newTab = Safari.Tab({url: "https://news.ycombinator.com"})
frontmostWindow.tabs.push(newTab)
frontmostWindow.currentTab = newTab
}
// Execute JavaScript in page
safari.doJavaScript("document.title", {in: safari.windows[0].currentTab})
'
osascript -l JavaScript -e '
const se = Application("System Events")
// List running apps
se.processes().map(p => p.name())
// Click menu item
const proc = se.processes.byName("Finder")
proc.menuBars[0].menuBarItems.byName("File").menus[0].menuItems.byName("New Finder Window").click()
// Keystroke
se.keystroke("v", {using: "command down"})
se.keyCode(36) // Return key
'
osascript -l JavaScript -e '
const app = Application.currentApplication()
app.includeStandardAdditions = true
app.displayNotification("Body text", {
withTitle: "Title",
subtitle: "Subtitle",
soundName: "Frog"
})
'
osascript -l JavaScript -e '
const app = Application.currentApplication()
app.includeStandardAdditions = true
// Alert
app.displayAlert("Title", {message: "Details", buttons: ["OK", "Cancel"]})
// Choose file
app.chooseFile({withPrompt: "Select a file"})
// Choose folder
app.chooseFolder({withPrompt: "Select folder"})
// Text input
app.displayDialog("Enter name:", {defaultAnswer: ""})
'
osascript -l JavaScript -e '
const app = Application.currentApplication()
app.includeStandardAdditions = true
// Get clipboard
app.theClipboard()
// Set clipboard
app.setTheClipboardTo("New content")
'
osascript -l JavaScript -e '
const cal = Application("Calendar")
// List calendars
cal.calendars().map(c => c.name())
// Create event
const calendar = cal.calendars.byName("Home")
cal.Event({
summary: "Meeting",
startDate: new Date("2024-01-15T10:00:00"),
endDate: new Date("2024-01-15T11:00:00")
}).make({at: calendar})
'
osascript -l JavaScript -e '
const rem = Application("Reminders")
// List reminder lists
rem.lists().map(l => l.name())
// Create reminder
const list = rem.lists.byName("Reminders")
rem.Reminder({name: "Buy groceries", dueDate: new Date()}).make({at: list})
'
sdef /path/to/app.app to see available commandsapp.includeStandardAdditions = true for dialogs, file ops.map() to extract valuesconsole.log() and run with osascript -l JavaScript script.jsosascript -l JavaScript -e '
try {
const music = Application("Music")
music.currentTrack.name()
} catch(e) {
"No track playing"
}
'
testing
Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
development
Use when writing Zig code. Contains Zig 0.15 API changes and patterns.
development
Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like "the xlsx in my downloads") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.
tools
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.