Skip to content

Instantly share code, notes, and snippets.

@drewkerr
Last active October 10, 2025 07:11
Show Gist options
  • Save drewkerr/0f2b61ce34e2b9e3ce0ec6a92ab05c18 to your computer and use it in GitHub Desktop.
Save drewkerr/0f2b61ce34e2b9e3ce0ec6a92ab05c18 to your computer and use it in GitHub Desktop.
Read the current Focus mode on macOS Monterey (12.0+) using JavaScript for Automation (JXA)
const app = Application.currentApplication()
app.includeStandardAdditions = true
function getJSON(path) {
const fullPath = path.replace(/^~/, app.pathTo('home folder'))
const contents = app.read(fullPath)
return JSON.parse(contents)
}
function run() {
let focus = "No focus" // default
const assert = getJSON("~/Library/DoNotDisturb/DB/Assertions.json").data[0].storeAssertionRecords
const config = getJSON("~/Library/DoNotDisturb/DB/ModeConfigurations.json").data[0].modeConfigurations
if (assert) { // focus set manually
const modeid = assert[0].assertionDetails.assertionDetailsModeIdentifier
focus = config[modeid].mode.name
} else { // focus set by trigger
const date = new Date
const now = date.getHours() * 60 + date.getMinutes()
for (const modeid in config) {
const triggers = config[modeid].triggers.triggers[0]
if (triggers && triggers.enabledSetting == 2) {
const start = triggers.timePeriodStartTimeHour * 60 + triggers.timePeriodStartTimeMinute
const end = triggers.timePeriodEndTimeHour * 60 + triggers.timePeriodEndTimeMinute
if (start < end) {
if (now >= start && now < end) {
focus = config[modeid].mode.name
}
} else if (start > end) { // includes midnight
if (now >= start || now < end) {
focus = config[modeid].mode.name
}
}
}
}
}
return focus
}
@drewkerr
Copy link
Author

drewkerr commented Jul 4, 2023

Not sure. It's working for me on the same version, running in Script Editor and Shortcuts, as long as Full Disk Access is allowed in System Settings (siriactionsd, in the case of Shortcuts). This isn't meant to be much more than a hack though.

@devnoname120
Copy link

devnoname120 commented Jul 5, 2023

I use the following Alfred Workflow in order to enable/disable DND: https://github.com/vitorgalvao/calm-notifications-workflow/tree/main/Workflow

It uses its own Shortcut under the hood that needs to be installed first.

I post it here as an example of a working implementation that people can look into.

@roman-ld
Copy link

Python if you want in that context.

#!/usr/bin/env python3

import json
import os
import datetime

ASSERT_PATH = os.path.expanduser("~/Library/DoNotDisturb/DB/Assertions.json")
MODECONFIG_PATH = os.path.expanduser("~/Library/DoNotDisturb/DB/ModeConfigurations.json")
def get_focus():
    focus = "No focus" #default
    assertJ = json.load(open(ASSERT_PATH))['data'][0]['storeAssertionRecords']
    configJ = json.load(open(MODECONFIG_PATH))['data'][0]['modeConfigurations']
    if assertJ:
        modeid = assertJ[0]['assertionDetails']['assertionDetailsModeIdentifier']
        focus = configJ[modeid]['mode']['name']
    else:
        date = datetime.datetime.today()
        now = date.hour * 60 + date.minute

        for modeid in configJ:
            triggers = configJ[modeid]['triggers']['triggers'][0]
            if triggers and triggers['enabledSetting'] == 2:
                start = triggers['timePeriodStartTimeHour'] * 60 + triggers['timePeriodStartTimeMinute']
                end = triggers['timePeriodEndTimeHour'] * 60 + triggers['timePeriodEndTimeMinute']
                if start < end:
                    if now >= start and now < end:
                        focus = configJ[modeid]['mode']['name']
                elif start > end: # includes midnight
                    if now >= start or now < end:
                        focus = configJ[modeid]['mode']['name']
    return focus

if '__main__' == __name__:
    print(get_focus())

@tooh
Copy link

tooh commented Sep 17, 2025

@roman-ld Had this script running on Sequioa. Upgraded last weekend to Tahoe. Script is now broken, info is not anymore in Assertions. Would appreciate a fix.

@roman-ld
Copy link

roman-ld commented Sep 17, 2025

@tooh welcome to macos updates!
Still working for me after upgrading to Tahoe.

I think I've found the problem, have you agreed to the "Xcode and Apple SDKs license" after upgrading?

I looked at what my python3 I was using (which python3).
Found I'm on homebrew for python3 (meaning I didn't have to agree for the python to work).
Used find to find other python3 on my path (find "${(s/:/)PATH}" -name python3).

Aside: Apple's zsh now has several entries that are not readable / search able from a non-admin account (don't worry about these if you run into this as a non-admin user).

find: /usr/sbin/authserver: Permission denied
find: /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin: No such file or directory
find: /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin: No such file or directory
find: /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin: No such file or directory
find: /Library/Apple/usr/bin: No such file or directory

When I switched the #! to #!/usr/bin/python3 (macos stock python3) I got:

You have not agreed to the Xcode and Apple SDKs license.
...

Bottom of that says:

Agreeing to the Xcode and Apple SDKs license requires admin privileges, please accept the Xcode license as the root user (e.g. 'sudo xcodebuild -license').

After agreeing (running the -license, typing accept) the script worked with macos stock python3

I hope this helps.

@luckman212
Copy link

luckman212 commented Sep 24, 2025

I just needed something dead-simple: focus mode on or off. None of the above were working without errors for me on Tahoe, so I adapted the Python example from @roman-ld above (thanks) and stripped out some parts that I don't need.

I saved this as /usr/local/bin/dnd and chmod +x'd it. “Works for me” but YMMV.

With this you can do things like if dnd -q ; then ... ; fi in shell scripts.

#!/usr/bin/env python3

"""
Check DND status - tested only on macOS 26.0 Tahoe
exits with 0 if a focus mode is enabled, 1 otherwise
prints mode unless -q/--quiet is passed as arg

based on https://gist.github.com/drewkerr/0f2b61ce34e2b9e3ce0ec6a92ab05c18

"""

import json
import os
import sys

ASSERTIONS_PATH = os.path.expanduser("~/Library/DoNotDisturb/DB/Assertions.json")
CONFIG_PATH = os.path.expanduser("~/Library/DoNotDisturb/DB/ModeConfigurations.json")
QUIET = False

def msg(m: str, q: bool) -> None:
	if not q:
		print(m)

def get_focus(q: bool) -> int:
	try:
		ASSERTION_RECS = json.load(open(ASSERTIONS_PATH))['data'][0]['storeAssertionRecords']
		FOCUS_CONFIGS = json.load(open(CONFIG_PATH))['data'][0]['modeConfigurations']
		MODE = ASSERTION_RECS[0]['assertionDetails']['assertionDetailsModeIdentifier']
		msg(FOCUS_CONFIGS[MODE]['mode']['name'], q)
		return 0
	except:
		msg("No focus", q)
		return 1

if '__main__' == __name__:
	if len(sys.argv) > 1:
		if sys.argv[1] in ['-q', '--quiet']:
			QUIET = True
	sys.exit(get_focus(QUIET))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment