Skip to content

Instantly share code, notes, and snippets.

@dnicolson
Last active May 10, 2026 18:25
Show Gist options
  • Select an option

  • Save dnicolson/de1b8a7d0ee1bb31b09eb7f8afd38a65 to your computer and use it in GitHub Desktop.

Select an option

Save dnicolson/de1b8a7d0ee1bb31b09eb7f8afd38a65 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
#
# Add an app bundle to the Finder toolbar without replacing the stock items.
#
# Usage:
# ./add-to-finder-toolbar.sh /Applications/SomeApp.app POSITION
# POSITION is 0-based; use -1 for the last position.
set -euo pipefail
PREF_DOMAIN="com.apple.finder"
if [[ $# -ne 2 ]]; then
echo "Usage: $0 /path/to/App.app POSITION" >&2
echo "POSITION is 0-based; use -1 for the last position." >&2
exit 1
fi
TARGET="$1"
POSITION="$2"
if [[ ! -d "$TARGET" ]]; then
echo "Error: '$TARGET' is not an app bundle." >&2
exit 1
fi
if ! [[ "$POSITION" =~ ^-?[0-9]+$ ]]; then
echo "Error: POSITION must be an integer." >&2
exit 1
fi
TARGET="$(cd "$(dirname "$TARGET")" && pwd)/$(basename "$TARGET")"
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
prefs_in="$tmpdir/finder-in.plist"
prefs_out="$tmpdir/finder-out.plist"
defaults export "$PREF_DOMAIN" - > "$prefs_in"
python3 - "$TARGET" "$POSITION" "$prefs_in" "$prefs_out" <<'PY'
import copy
import plistlib
import re
import subprocess
import sys
from pathlib import Path
APP_PATH = sys.argv[1]
POSITION = int(sys.argv[2])
PREFS_IN = Path(sys.argv[3])
PREFS_OUT = Path(sys.argv[4])
IDENTIFIER = "com.apple.finder.loc "
APPKIT_KEY = "NSToolbar Configuration Browser"
FINDER_KEY = "FXInfoPanesExpanded"
FINDER_BINARY = "/System/Library/CoreServices/Finder.app/Contents/MacOS/Finder"
def bail(message):
print(f"ERROR: {message}", file=sys.stderr)
sys.exit(1)
def info(message):
print(message, file=sys.stderr)
def cloned_dict(value):
return copy.deepcopy(value) if isinstance(value, dict) else {}
def normalized_identifiers(value):
if isinstance(value, list):
return list(value)
if isinstance(value, dict):
def sort_key(item_key):
try:
return int(item_key)
except (TypeError, ValueError):
return item_key
return [value[key] for key in sorted(value, key=sort_key)]
return []
def effective_identifiers(config):
identifiers = normalized_identifiers(config.get("TB Item Identifiers"))
if identifiers:
return identifiers
defaults = normalized_identifiers(config.get("TB Default Item Identifiers"))
if defaults:
return defaults
return []
def identifier_from_kind_token(token):
special_tokens = {
"spac": "NSToolbarSpaceItem",
"sSpc": "NSToolbarSpaceItem",
"sepa": "NSToolbarSeparatorItemIdentifier",
"flxs": "NSToolbarFlexibleSpaceItemIdentifier",
"CMTB": "NSToolbarCustomizeToolbarItemIdentifier",
}
if token in special_tokens:
return special_tokens[token]
if re.fullmatch(r"[A-Z]{4}", token):
return f"com.apple.finder.{token}"
return None
def decode_kind_runs(blob):
runs = []
for offset in range(4):
run = []
for index in range(offset, len(blob) - 3, 4):
token = blob[index:index + 4][::-1]
identifier = identifier_from_kind_token(token)
if identifier is None:
if run:
runs.append(run)
run = []
continue
run.append(identifier)
if run:
runs.append(run)
return runs
def finder_binary_default_identifiers():
try:
result = subprocess.run(
["otool", "-s", "__TEXT", "__const", FINDER_BINARY],
capture_output=True,
check=True,
text=True,
)
except (OSError, subprocess.CalledProcessError):
return []
cstring_bytes = bytearray()
for line in result.stdout.splitlines():
parts = line.strip().split()
if len(parts) < 2 or not re.fullmatch(r"[0-9A-Fa-f]{16}", parts[0]):
continue
for word in parts[1:]:
if re.fullmatch(r"[0-9A-Fa-f]{8}", word):
cstring_bytes.extend(bytes.fromhex(word)[::-1])
exact_match_count = 10
decoded = cstring_bytes.decode("latin-1", errors="ignore")
for candidate in re.findall(r"[A-Za-z]{20,}", decoded):
for run in decode_kind_runs(candidate):
if len(run) >= exact_match_count:
return run[:exact_match_count]
return []
with PREFS_IN.open("rb") as handle:
prefs = plistlib.load(handle)
finder_config = prefs.get(FINDER_KEY)
appkit_config = prefs.get(APPKIT_KEY)
if isinstance(appkit_config, dict):
config = cloned_dict(appkit_config)
info(f"Found '{APPKIT_KEY}' — using as base.")
if isinstance(finder_config, dict):
for key, value in finder_config.items():
config[key] = copy.deepcopy(value)
info(f"Overlaying custom items from '{FINDER_KEY}'.")
elif isinstance(finder_config, dict):
config = cloned_dict(finder_config)
info(f"'{APPKIT_KEY}' absent — using '{FINDER_KEY}' as base.")
else:
bail(
"Neither 'FXInfoPanesExpanded' nor 'NSToolbar Configuration Browser' "
"found in com.apple.finder."
)
identifiers = effective_identifiers(config)
raw_plists = config.get("TB Item Plists", {})
item_plists = dict(raw_plists) if raw_plists else {}
if not identifiers:
identifiers = finder_binary_default_identifiers()
if not identifiers:
bail(
"Finder does not currently expose toolbar identifiers in prefs, "
"and the default toolbar could not be derived from Finder.app."
)
info("Derived the default toolbar from Finder.app.")
info(f"Current toolbar ({len(identifiers)} items): {identifiers}")
file_url = "file://" + APP_PATH.rstrip("/") + "/"
existing_index = None
for index, identifier in enumerate(identifiers):
if identifier != IDENTIFIER:
continue
entry = item_plists.get(str(index))
if isinstance(entry, dict) and entry.get("_CFURLString") == file_url:
existing_index = index
break
if IDENTIFIER in identifiers:
duplicate_index = identifiers.index(IDENTIFIER)
identifiers.pop(duplicate_index)
new_plists = {}
for key, value in item_plists.items():
try:
numeric_key = int(key)
except (TypeError, ValueError):
new_plists[key] = value
continue
if numeric_key < duplicate_index:
new_plists[str(numeric_key)] = value
elif numeric_key > duplicate_index:
new_plists[str(numeric_key - 1)] = value
item_plists = new_plists
if POSITION == -1:
insert_index = len(identifiers)
else:
insert_index = max(0, min(POSITION, len(identifiers)))
if existing_index is not None and existing_index == insert_index:
info(f"'{APP_PATH}' is already present at position {insert_index}. Nothing to do.")
sys.exit(0)
identifiers.insert(insert_index, IDENTIFIER)
new_plists = {}
for key, value in item_plists.items():
try:
numeric_key = int(key)
except (TypeError, ValueError):
new_plists[key] = value
continue
if numeric_key >= insert_index:
new_plists[str(numeric_key + 1)] = value
else:
new_plists[str(numeric_key)] = value
new_plists[str(insert_index)] = {
"_CFURLString": file_url,
"_CFURLStringType": 15,
}
item_plists = new_plists
config["TB Item Identifiers"] = identifiers
config["TB Item Plists"] = item_plists
finder_out = cloned_dict(finder_config)
appkit_out = cloned_dict(appkit_config) or cloned_dict(config)
finder_out["TB Item Identifiers"] = copy.deepcopy(identifiers)
finder_out["TB Item Plists"] = copy.deepcopy(item_plists)
appkit_out.update(copy.deepcopy(config))
appkit_out["TB Item Identifiers"] = copy.deepcopy(identifiers)
appkit_out["TB Item Plists"] = copy.deepcopy(item_plists)
prefs[FINDER_KEY] = finder_out
prefs[APPKIT_KEY] = appkit_out
with PREFS_OUT.open("wb") as handle:
plistlib.dump(prefs, handle, fmt=plistlib.FMT_XML)
info(f"Written: index {insert_index} → '{IDENTIFIER}' → {file_url}")
PY
if [[ ! -s "$prefs_out" ]]; then
exit 0
fi
defaults import "$PREF_DOMAIN" "$prefs_out"
echo "Restarting Finder..."
killall Finder >/dev/null 2>&1 || true
echo "Done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment