Dynamically set Focus Mode based on variable/user input

Description of what you are sharing

Problem

I want to be able to set my current Focus Mode dynamically based on user input. e.g.

shortcuts run "Set_Focus_Mode" <<< "Work"
shortcuts run "Set_Focus_Mode" <<< "Reduce Interruptions"
shortcuts run "Set_Focus_Mode" <<< "No Focus"

Or, via BTT:

Unfortunately, it's not possible to set the name of a Focus Mode dynamically from a variable (see screenshot below).

Impact

This requires manually building an Apple Shortcut with many deeply nested If statements, one for each Focus Mode you have uniquely configured in System Settings – like so:

If {Shortcut Input} is {Name of Focus Mode N^1}
    Turn {Name of the Focus Mode N^1} On until Turn Off
Otherwise
    If {Shortcut Input} is {Name of a Focus Mode N^2}
        Turn {Name of the Focus Mode N^2} On until Turn Off
    Otherwise
        If {Shortcut Input} is {Name of a Focus Mode N^3}
            Turn {Name of the Focus Mode N^3} On until Turn Off
        Otherwise
            ... repeat for each of your Focus Modes,
                i.e. until N^a where a == number of 
                Focus Modes you've configured.

This is tedious to build. Furthermore, once built, it cannot simply be shared with others since everyone has their own unique set of Focus Modes.

Solution

Programmatically build the Set_Focus_Mode Shortcut based on the your unique set of Focus Modes. The script will pull your unique set of Focus Modes from ~/Library/DoNotDisturb/DB/ModeConfigurations.json , e.g.:

cat ~/Library/DoNotDisturb/DB/ModeConfigurations.json | \
jq '
  .data[0].modeConfigurations
  | to_entries
  | map({
      key: .value.mode.name,
      value: .key
    })
  | from_entries
'
{
  "Gaming": "com.apple.focus.gaming",
  "Sleep": "com.apple.sleep.sleep-mode",
  "Reading": "com.apple.focus.reading",
  "Fitness": "com.apple.donotdisturb.mode.workout",
  "Driving": "com.apple.donotdisturb.mode.driving",
  "Do Not Disturb": "com.apple.donotdisturb.mode.default",
  "Mindfulness": "com.apple.focus.mindfulness",
  "Personal": "com.apple.focus.personal-time",
  "Reduce Interruptions": "com.apple.focus.reduce-interruptions",
  "Work": "com.apple.focus.work"
}

Here's a shell script that will automatically create the Shortcut, sign it, and open it in the Shortcuts app.

#!/usr/bin/env bash
set -euo pipefail

# ─── Logging Helpers ────────────────────────────────────────────────────────────
info()  { printf '\033[1;32m[INFO]\033[0m  %s\n' "$*"; }
warn()  { printf '\033[1;33m[WARN]\033[0m  %s\n' "$*"; }
error() { printf '\033[1;31m[ERROR]\033[0m %s\n' "$*"; }
debug() { printf '\033[1;34m[DEBUG]\033[0m %s\n' "$*"; }

# ─── 1. Paths & filenames ─────────────────────────────────────────────────────
CONFIG="${HOME}/Library/DoNotDisturb/DB/ModeConfigurations.json"
JSON_OUT="focus_mode.json"
TMP="focus_mode.tmp.json"
SHORTCUT="focus_mode.shortcut"
SIGNED="focus_mode-signed.shortcut"

info "Using config: $CONFIG"
info "Will build JSON at: $JSON_OUT"

# ─── 2. Start a minimal JSON skeleton ─────────────────────────────────────────
info "Initializing JSON skeleton"
cat > "$JSON_OUT" <<'EOF'
{
  "WFQuickActionSurfaces": [],
  "WFWorkflowActions": [],
  "WFWorkflowImportQuestions": [],
  "WFWorkflowTypes": [],
  "WFWorkflowHasShortcutInputVariables": true,
  "WFWorkflowMinimumClientVersionString": "1113",
  "WFWorkflowMinimumClientVersion": 1113,
  "WFWorkflowClientVersion": "3514.0.4.200",
  "WFWorkflowHasOutputFallback": true,
  "WFWorkflowIcon": {
    "WFWorkflowIconGlyphNumber": 59782,
    "WFWorkflowIconStartColor": 4274264319
  },
  "WFWorkflowInputContentItemClasses": ["WFAppContentItem","WFStringContentItem"],
  "WFWorkflowOutputContentItemClasses": ["WFStringContentItem"],
  "WFWorkflowNoInputBehavior": {
    "Name": "WFWorkflowNoInputBehaviorAskForInput",
    "Parameters": { "ItemClass": "WFStringContentItem" }
  }
}
EOF

# ─── 3. Read Focus Modes into bash arrays ──────────────────────────────────────
info "Reading focus modes from JSON"
declare -a NAMES IDS
while IFS=$'\t' read -r name id; do
  NAMES+=( "$name" )
  IDS+=( "$id" )
  debug "Found mode: '$name' -> $id"
done < <(
  jq -r '
    .data[0].modeConfigurations
    | to_entries[]
    | "\(.value.mode.name)\t\(.key)"
  ' "$CONFIG"
)
info "Total modes: ${#NAMES[@]}"

# ─── 4. Generate all the UUIDs we need ────────────────────────────────────────
info "Generating UUIDs"
trim_uuid=$(uuidgen | tr '[:lower:]' '[:upper:]')
debug "trim_uuid=$trim_uuid"
result_uuid=$(uuidgen | tr '[:lower:]' '[:upper:]')
debug "result_uuid=$result_uuid"

declare -a GIDS
for idx in "${!NAMES[@]}"; do
  gid=$(uuidgen | tr '[:lower:]' '[:upper:]')
  GIDS+=( "$gid" )
  debug "group $idx: ${NAMES[idx]} -> gid=$gid"
done

# Find which index corresponds to the built-in β€œDo Not Disturb” (default-off) mode
DEFAULT_IDX=0
for i in "${!IDS[@]}"; do
  if [[ "${IDS[i]}" == "com.apple.donotdisturb.mode.default" ]]; then
    DEFAULT_IDX=$i
    debug "Default‐off mode at index $i (${NAMES[i]})"
  fi
done
no_focus_gid=${GIDS[$DEFAULT_IDX]}
info "No-Focus group ID: $no_focus_gid"

# ─── 5. Append the β€œTrim Whitespace” action ─────────────────────────────────
info "Appending Trim Whitespace action"
jq --arg tu "$trim_uuid" '
  .WFWorkflowActions += [
    {
      "WFWorkflowActionIdentifier":"is.workflow.actions.text.trimwhitespace",
      "WFWorkflowActionParameters":{
        "UUID":$tu,
        "WFInput":{
          "WFSerializationType":"WFTextTokenString",
          "Value":{
            "string":"\uFFFC",
            "attachmentsByRange":{ "{0, 1}":{ "Type":"ExtensionInput" } }
          }
        }
      }
    }
  ]
' "$JSON_OUT" > "$TMP" && mv "$TMP" "$JSON_OUT"

# ─── 6. Append the β€œNo Focus” branch ────────────────────────────────────────
info "Appending No Focus branch"
jq --arg tu "$trim_uuid" --arg gid "$no_focus_gid" '
  .WFWorkflowActions += [
    {
      "WFWorkflowActionIdentifier":"is.workflow.actions.conditional",
      "WFWorkflowActionParameters":{
        "GroupingIdentifier":$gid,
        "WFCondition":4,
        "WFConditionalActionString":"No Focus",
        "WFControlFlowMode":0,
        "WFInput":{
          "Type":"Variable",
          "Variable":{
            "WFSerializationType":"WFTextTokenAttachment",
            "Value":{
              "Type":"ActionOutput",
              "OutputName":"Updated Text",
              "OutputUUID":$tu
            }
          }
        }
      }
    },
    {
      "WFWorkflowActionIdentifier":"is.workflow.actions.dnd.set",
      "WFWorkflowActionParameters":{}
    },
    {
      "WFWorkflowActionIdentifier":"is.workflow.actions.conditional",
      "WFWorkflowActionParameters":{
        "GroupingIdentifier":$gid,
        "WFControlFlowMode":1
      }
    }
  ]
' "$JSON_OUT" > "$TMP" && mv "$TMP" "$JSON_OUT"

# ─── 7. Dynamically append one IFβ†’SETβ†’END block per mode ────────────────────
info "Appending per-mode branches"
for i in "${!NAMES[@]}"; do
  name=${NAMES[i]}
  id=${IDS[i]}
  gid=${GIDS[i]}
  info "  β€’ $name (id=$id, gid=$gid)"

  jq \
    --arg name "$name" \
    --arg id   "$id" \
    --arg gid  "$gid" \
    --arg tu   "$trim_uuid" \
  '
    .WFWorkflowActions += [
      {
        "WFWorkflowActionIdentifier":"is.workflow.actions.conditional",
        "WFWorkflowActionParameters":{
          "GroupingIdentifier":$gid,
          "WFCondition":4,
          "WFConditionalActionString":$name,
          "WFControlFlowMode":0,
          "WFInput":{
            "Type":"Variable",
            "Variable":{
              "WFSerializationType":"WFTextTokenAttachment",
              "Value":{
                "Type":"ActionOutput",
                "OutputName":"Updated Text",
                "OutputUUID":$tu
              }
            }
          }
        }
      },
      {
        "WFWorkflowActionIdentifier":"is.workflow.actions.dnd.set",
        "WFWorkflowActionParameters":
          ( if $id == "com.apple.donotdisturb.mode.default"
            then { "Enabled":1 }
            else {
              "Enabled":1,
              "FocusModes":{
                "Identifier":$id,
                "DisplayString":$name
              }
            }
            end
          )
      },
      {
        "WFWorkflowActionIdentifier":"is.workflow.actions.conditional",
        "WFWorkflowActionParameters":{
          "GroupingIdentifier":$gid,
          "WFControlFlowMode":1
        }
      }
    ]
  ' "$JSON_OUT" > "$TMP" && mv "$TMP" "$JSON_OUT"
done

# ─── 8. Fallback alert ───────────────────────────────────────────────────────
info "Appending fallback alert"
jq --arg tu "$trim_uuid" '
  .WFWorkflowActions += [
    {
      "WFWorkflowActionIdentifier":"is.workflow.actions.alert",
      "WFWorkflowActionParameters":{
        "WFAlertActionTitle":"Do you want to continue?",
        "WFAlertActionMessage":{
          "WFSerializationType":"WFTextTokenString",
          "Value":{
            "string":"\uFFFC",
            "attachmentsByRange":{ "{0, 1}":{
              "Type":"ActionOutput",
              "OutputName":"Updated Text",
              "OutputUUID":$tu
            }}
          }
        }
      }
    }
  ]
' "$JSON_OUT" > "$TMP" && mv "$TMP" "$JSON_OUT"

# ─── 9. Close all IF blocks (in reverse order), tagging default branch ───────
info "Closing all conditionals"
for (( idx=${#GIDS[@]}-1; idx>=0; idx-- )); do
  gid=${GIDS[idx]}
  debug "Closing group $gid"
  jq \
    --arg gid "$gid" \
    --arg ru  "$result_uuid" \
    --arg dg  "$no_focus_gid" \
  '
    .WFWorkflowActions += [
      {
        "WFWorkflowActionIdentifier":"is.workflow.actions.conditional",
        "WFWorkflowActionParameters":
          ( if $gid == $dg
            then { "GroupingIdentifier":$gid, "WFControlFlowMode":2, "UUID":$ru }
            else { "GroupingIdentifier":$gid, "WFControlFlowMode":2 }
            end
          )
      }
    ]
  ' "$JSON_OUT" > "$TMP" && mv "$TMP" "$JSON_OUT"
done

# ─── 10. Final output action ────────────────────────────────────────────────
info "Appending final output action"
jq --arg ru "$result_uuid" '
  .WFWorkflowActions += [
    {
      "WFWorkflowActionIdentifier":"is.workflow.actions.output",
      "WFWorkflowActionParameters":{
        "WFNoOutputSurfaceBehavior":"Respond",
        "WFOutput":{
          "WFSerializationType":"WFTextTokenString",
          "Value":{
            "string":"\uFFFC",
            "attachmentsByRange":{ "{0, 1}":{
              "Type":"ActionOutput",
              "OutputName":"If Result",
              "OutputUUID":$ru
            }}
          }
        },
        "WFResponse":{
          "WFSerializationType":"WFTextTokenString",
          "Value":{
            "string":"\uFFFC",
            "attachmentsByRange":{ "{0, 1}":{
              "Type":"ActionOutput",
              "OutputName":"If Result",
              "OutputUUID":$ru
            }}
          }
        }
      }
    }
  ]
' "$JSON_OUT" > "$TMP" && mv "$TMP" "$JSON_OUT"

# ─── 11. Convert JSON β†’ binary .shortcut, then sign & open ─────────────────
info "Converting JSON β†’ binary .shortcut"
if plutil -convert binary1 -o "$SHORTCUT" "$JSON_OUT"; then
  info "  β†’ wrote $SHORTCUT"
else
  error "plutil conversion failed"
  exit 1
fi

info "Signing shortcut"
if shortcuts sign --mode anyone --input "$SHORTCUT" --output "$SIGNED"; then
  info "  β†’ signed to $SIGNED"
else
  warn "Signing failed; please ensure Shortcuts CLI is installed"
fi

info "Opening signed shortcut"
if open "$SIGNED"; then
  info "  β†’ opened $SIGNED"
else
  warn "Failed to open $SIGNED"
fi

info "πŸŽ‰ Done! Generated, signed, and opened: $SIGNED"

Setup Instructions

  1. Save the shell script to a file, e.g. focus_mode.sh

  2. Make the shell script executable by running the following in your preferred terminal app:
    chmod +x focus_mode.sh

    Note: set full path of focus_mode.sh if necessary

  3. Run the shell script:
    ./focus_mode.sh

    Here's the log output when I ran the script:

    [INFO]  Using config: ~/Library/DoNotDisturb/DB/ModeConfigurations.json
    [INFO]  Will build JSON at: focus_mode.json
    [INFO]  Initializing JSON skeleton
    [INFO]  Reading focus modes from JSON
    [DEBUG] Found mode: 'Gaming' -> com.apple.focus.gaming
    [DEBUG] Found mode: 'Sleep' -> com.apple.sleep.sleep-mode
    [DEBUG] Found mode: 'Reading' -> com.apple.focus.reading
    [DEBUG] Found mode: 'Fitness' -> com.apple.donotdisturb.mode.workout
    [DEBUG] Found mode: 'Driving' -> com.apple.donotdisturb.mode.driving
    [DEBUG] Found mode: 'Do Not Disturb' -> com.apple.donotdisturb.mode.default
    [DEBUG] Found mode: 'Mindfulness' -> com.apple.focus.mindfulness
    [DEBUG] Found mode: 'Personal' -> com.apple.focus.personal-time
    [DEBUG] Found mode: 'Reduce Interruptions' -> com.apple.focus.reduce-interruptions
    [DEBUG] Found mode: 'Work' -> com.apple.focus.work
    [INFO]  Total modes: 10
    [INFO]  Generating UUIDs
    [DEBUG] trim_uuid=46C3EA76-2668-4034-BC79-9FA45D7980DA
    [DEBUG] result_uuid=39163F06-556B-4676-8FBD-78AA37E1849D
    [DEBUG] group 0: Gaming -> gid=612344E1-16E4-4F0B-A10D-3A71948B92E1
    [DEBUG] group 1: Sleep -> gid=A03833F9-1F60-405A-8F60-AEAA033BC4E9
    [DEBUG] group 2: Reading -> gid=601CBDF1-B667-4960-9A9C-29AFD9B5A9D7
    [DEBUG] group 3: Fitness -> gid=53C04063-ECBB-40F7-ABCE-AE436C1717DC
    [DEBUG] group 4: Driving -> gid=1D40BB7F-82B9-43E4-A428-7BBD3EFF2123
    [DEBUG] group 5: Do Not Disturb -> gid=E8A0EE75-A270-4FC6-89DA-64BE67DE962D
    [DEBUG] group 6: Mindfulness -> gid=29001490-CA3B-4963-9ABB-363BAB04DC82
    [DEBUG] group 7: Personal -> gid=FA959250-D897-4B22-9C70-BC5D429AC29D
    [DEBUG] group 8: Reduce Interruptions -> gid=B670DCF2-7321-4FB4-A0E6-8E51B0713061
    [DEBUG] group 9: Work -> gid=C229B6CC-913C-4474-9CA5-04A3625CE6BC
    [DEBUG] Default‐off mode at index 5 (Do Not Disturb)
    [INFO]  No-Focus group ID: E8A0EE75-A270-4FC6-89DA-64BE67DE962D
    [INFO]  Appending Trim Whitespace action
    [INFO]  Appending No Focus branch
    [INFO]  Appending per-mode branches
    [INFO]    β€’ Gaming (id=com.apple.focus.gaming, gid=612344E1-16E4-4F0B-A10D-3A71948B92E1)
    [INFO]    β€’ Sleep (id=com.apple.sleep.sleep-mode, gid=A03833F9-1F60-405A-8F60-AEAA033BC4E9)
    [INFO]    β€’ Reading (id=com.apple.focus.reading, gid=601CBDF1-B667-4960-9A9C-29AFD9B5A9D7)
    [INFO]    β€’ Fitness (id=com.apple.donotdisturb.mode.workout, gid=53C04063-ECBB-40F7-ABCE-AE436C1717DC)
    [INFO]    β€’ Driving (id=com.apple.donotdisturb.mode.driving, gid=1D40BB7F-82B9-43E4-A428-7BBD3EFF2123)
    [INFO]    β€’ Do Not Disturb (id=com.apple.donotdisturb.mode.default, gid=E8A0EE75-A270-4FC6-89DA-64BE67DE962D)
    [INFO]    β€’ Mindfulness (id=com.apple.focus.mindfulness, gid=29001490-CA3B-4963-9ABB-363BAB04DC82)
    [INFO]    β€’ Personal (id=com.apple.focus.personal-time, gid=FA959250-D897-4B22-9C70-BC5D429AC29D)
    [INFO]    β€’ Reduce Interruptions (id=com.apple.focus.reduce-interruptions, gid=B670DCF2-7321-4FB4-A0E6-8E51B0713061)
    [INFO]    β€’ Work (id=com.apple.focus.work, gid=C229B6CC-913C-4474-9CA5-04A3625CE6BC)
    [INFO]  Appending fallback alert
    [INFO]  Closing all conditionals
    [DEBUG] Closing group C229B6CC-913C-4474-9CA5-04A3625CE6BC
    [DEBUG] Closing group B670DCF2-7321-4FB4-A0E6-8E51B0713061
    [DEBUG] Closing group FA959250-D897-4B22-9C70-BC5D429AC29D
    [DEBUG] Closing group 29001490-CA3B-4963-9ABB-363BAB04DC82
    [DEBUG] Closing group E8A0EE75-A270-4FC6-89DA-64BE67DE962D
    [DEBUG] Closing group 1D40BB7F-82B9-43E4-A428-7BBD3EFF2123
    [DEBUG] Closing group 53C04063-ECBB-40F7-ABCE-AE436C1717DC
    [DEBUG] Closing group 601CBDF1-B667-4960-9A9C-29AFD9B5A9D7
    [DEBUG] Closing group A03833F9-1F60-405A-8F60-AEAA033BC4E9
    [DEBUG] Closing group 612344E1-16E4-4F0B-A10D-3A71948B92E1
    [INFO]  Appending final output action
    [INFO]  Converting JSON β†’ binary .shortcut
    [INFO]    β†’ wrote focus_mode.shortcut
    [INFO]  Signing shortcut
    ERROR: Unrecognized attribute string flag '?' in attribute string "T@"NSString",?,R,C" for property debugDescription
    ERROR: Unrecognized attribute string flag '?' in attribute string "T@"NSString",?,R,C" for property debugDescription
    ERROR: Unrecognized attribute string flag '?' in attribute string "T@"NSString",?,R,C" for property debugDescription
    ERROR: Unrecognized attribute string flag '?' in attribute string "T@"NSString",?,R,C" for property debugDescription
    ERROR: Unrecognized attribute string flag '?' in attribute string "T@"NSString",?,R,C" for property debugDescription
    [INFO]    β†’ signed to focus_mode-signed.shortcut
    [INFO]  Opening signed shortcut
    [INFO]    β†’ opened focus_mode-signed.shortcut
    [INFO]  πŸŽ‰ Done! Generated, signed, and opened: focus_mode-signed.shortcut
    
  4. Enjoy your personalized Shortcut to dynamically set your Focus Modes :slight_smile:

Combining this Shortcut with BTT enables a lot of useful and creative opportunities.

All feedback and questions are welcome!

Great work!

1 Like