- migrate/install apps
- create app
- show app info
- check appstore version
- input method auto switch
- others
migrate/install apps
[!NOTE|label:references:]
"APP.app" is damaged and can't be opened. You should move it to the Trashroot cause:
scp -rflattened the symlinks insideSparkle.frameworkinto duplicate real files (the links are no longer links), which broke the code-signature seal and made Gatekeeper flag the app as "damaged."
# ── with ditto ────────────────────
# source server
$ ditto -c -k --keepParent ~/Applications/<APP>.app <APP>.zip
# target server
$ scp <source>:/path/to/<APP>.zip .
$ ditto -x -k ./<APP>.zip ~/Applications/
# ── with rsync ────────────────────
$ rsync -a <source>:~/Applications/<APP>.app ~/Applications/
# ── tar + ssh ────────────────────
$ ssh <source> 'cd ~/Application && tar czf - <APP>.app' | tar xzf - -C ~/Applications/
# verify
$ spctl -a -vvv -t exec ~/Applications/<APP>.app
/Users/marslo/Applications/<APP>.app: accepted
source=Notarized Developer ID
origin=Developer ID Application: Lei Liu (CTW3P64G5P)
[!NOTE|label:resource fork, Finder information, or similar detritus not allowed:]
- error message:
resource fork, Finder information, or similar detritus not allowed Disallowed xattr com.apple.FinderInfo found on .../Updater.app- check more xattr
# ── error ──────────
$ codesign --verify --deep --strict --verbose=2 ~/Applications/<APP>.app
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/.
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/libswift_Concurrency.dylib
--validated:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/libswift_Concurrency.dylib
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Autoupdate
--validated:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Autoupdate
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Updater.app
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/XPCServices/Installer.xpc
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/XPCServices/Downloader.xpc
/Users/marslo/Applications/<APP>.app: resource fork, Finder information, or similar detritus not allowed
In subcomponent: /Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Updater.app
file with invalid attached data: Disallowed xattr com.apple.FinderInfo found on /Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Updater.app
# ── fix ────────────
$ xattr -rd com.apple.FinderInfo ~/Applications/<APP>.app
$ xattr -rd com.apple.ResourceFork ~/Applications/<APP>.app
# or clear all ( recursive + clear )
$ xattr -rc ~/Applications/<APP>.app
# ── result ────────
$ codesign --verify --deep --strict --verbose=2 ~/Applications/<APP>.app
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/.
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/libswift_Concurrency.dylib
--validated:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/libswift_Concurrency.dylib
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/XPCServices/Downloader.xpc
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Autoupdate
--validated:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Autoupdate
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Updater.app
--validated:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/XPCServices/Downloader.xpc
--prepared:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/XPCServices/Installer.xpc
--validated:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/XPCServices/Installer.xpc
--validated:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Updater.app
--validated:/Users/marslo/Applications/<APP>.app/Contents/Frameworks/Sparkle.framework/Versions/Current/.
/Users/marslo/Applications/<APP>.app: valid on disk
/Users/marslo/Applications/<APP>.app: satisfies its Designated Requirement
install dmg apps
[!NOTE|label:referenees:]
$ hdiutil attach ~/Desktop/'Bartender 7.dmg'
$ ditto '/Volumes/Bartender 7/Bartender 7.app' '/Applications/Bartender 7.app'
$ codesign --verify --deep --strict '/Applications/Bartender 7.app' && echo VERIFY_OK
$ xattr -dr com.apple.quarantine '/Applications/Bartender 7.app'
# check info
$ hdiutil info
# detach/unmount
$ hdiutil detach '/Volumes/Bartender 7'
$ hdiutil detach -force '/Volumes/Bartender 7'
$ diskutil unmount '/Volumes/Bartender 7'
$ curl -O https://desktop.docker.com/mac/main/amd64/Docker.dmg
$ sudo hdiutil attach Docker.dmg
$ sudo /Volumes/Docker/Docker.app/Contents/MacOS/install
$ sudo hdiutil detach /Volumes/Docker
install pkg inside dmg
$ curl -fsSL -O https://download.oracle.com/java/21/latest/jdk-21_macos-x64_bin.dmg
$ hdiutil attach jdk-21_macos-x64_bin.dmg
$ sudo installer -pkg /Volumes/JDK\ 21.0.1/JDK\ 21.0.1.pkg -target /
$ sudo hdiutil detach /Volumes/JDK\ 21.0.1/
create app
[!NOTE|label:references:]
cleanup icon cache and rebuild
$ sudo rm -rf "/Library/Caches/com.apple.iconservices.store"
$ killall -KILL iconservicesd
$ killall Finder Dock
groovyConsole
[!NOTE|label:Expectation:] case: run
groovyConsolefrom Spotlight or Alfred
via Automator.app
[!NOTE|label:tips] Automator.app will create whole bunch of necessary files for app. only need to replace the
CFBundleExecutablefilename
Open Automator.app » New » Application

1.8.1.4.1 -- Automator.app » select Application Select Run Shell Script » save to
.app with empty shell script 
1.8.1.4.2 -- Automator.app » select Run Shell Script 
1.8.1.4.3 -- Automator.app » save to an app
via script
[!NOTE|label:tips:]
get standalone commands for the script
$ ps aux | grep groovyConsole | grep -v grep marslo 63030 0.0 1.9 42636292 310724 s008 S+ 2:06PM 0:12.48 /usr/local/opt/openjdk/bin/java -Dsun.awt.keepWorkingSetOnMinimize=true -Xdock:name=GroovyConsole -Xdock:icon=/usr/local/opt/groovy/libexec/lib/groovy.icns -classpath /usr/local/opt/groovy/libexec/lib/groovy-4.0.13.jar -Dscript.name=/usr/local/opt/groovy/libexec/bin/groovyConsole -Dprogram.name=groovyConsole -Dgroovy.starter.conf=/usr/local/opt/groovy/libexec/conf/groovy-starter.conf -Dgroovy.home=/usr/local/opt/groovy/libexec -Dtools.jar=/usr/local/opt/openjdk/lib/tools.jar org.codehaus.groovy.tools.GroovyStarter --main groovy.console.ui.Console --conf /usr/local/opt/groovy/libexec/conf/groovy-starter.conf --classpath .:/usr/local/opt/openjdk/lib/tools.jar:/usr/local/opt/openjdk/lib/dt.jar:/usr/local/opt/groovy/libexec/lib:.==> which would be:
/usr/local/opt/openjdk/bin/java \ -Dsun.awt.keepWorkingSetOnMinimize=true \ -Xdock:name=GroovyConsole \ -Xdock:icon=/usr/local/opt/groovy/libexec/lib/groovy.icns \ -classpath /usr/local/opt/groovy/libexec/lib/groovy-4.0.13.jar \ -Dscript.name=/usr/local/opt/groovy/libexec/bin/groovyConsole \ -Dprogram.name=groovyConsole \ -Dgroovy.starter.conf=/usr/local/opt/groovy/libexec/conf/groovy-starter.conf \ -Dgroovy.home=/usr/local/opt/groovy/libexec \ -Dtools.jar=/usr/local/opt/openjdk/lib/tools.jar org.codehaus.groovy.tools.GroovyStarter \ --main groovy.console.ui.Console \ --conf /usr/local/opt/groovy/libexec/conf/groovy-starter.conf \ --classpath .:/usr/local/opt/openjdk/lib/tools.jar:/usr/local/opt/openjdk/lib/dt.jar:/usr/local/opt/groovy/libexec/lib:.$ ps aux | grep groovyConsole | grep -v grep marslo 50495 0.0 3.4 11683536 577828 ?? S 5:50PM 0:15.85 /Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -Xdock:name=GroovyConsole -Xdock:icon=/usr/local/opt/groovy/libexec/lib/groovy.icns -Dgroovy.jaxb=jaxb -classpath /usr/local/opt/groovy/libexec/lib/groovy-3.0.6.jar -Dscript.name=/usr/local/opt/groovy/libexec/bin/groovyConsole -Dprogram.name=groovyConsole -Dgroovy.starter.conf=/usr/local/opt/groovy/libexec/conf/groovy-starter.conf -Dgroovy.home=/usr/local/opt/groovy/libexec -Dtools.jar=/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar org.codehaus.groovy.tools.GroovyStarter --main groovy.console.ui.Console --conf /usr/local/opt/groovy/libexec/conf/groovy-starter.conf --classpath .:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/dt.jar:/usr/local/opt/groovy/libexec/lib:.==> which would be:
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java \ -Xdock:name=GroovyConsole \ -Xdock:icon=/usr/local/opt/groovy/libexec/lib/groovy.icns \ -Dgroovy.jaxb=jaxb \ -classpath /usr/local/opt/groovy/libexec/lib/groovy-3.0.6.jar \ -Dscript.name=/usr/local/opt/groovy/libexec/bin/groovyConsole \ -Dprogram.name=groovyConsole \ -Dgroovy.starter.conf=/usr/local/opt/groovy/libexec/conf/groovy-starter.conf \ -Dgroovy.home=/usr/local/opt/groovy/libexec \ -Dtools.jar=/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar org.codehaus.groovy.tools.GroovyStarter \ --main groovy.console.ui.Console \ --conf /usr/local/opt/groovy/libexec/conf/groovy-starter.conf \ --classpath .:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/dt.jar:/usr/local/opt/groovy/libexec/lib:.
register openjdk:
# -- -v 21 -- $ brew install openjdk@21 $ sudo ln -sfn "$(brew --prefix openjdk@21)/libexec/openjdk.jdk" /Library/Java/JavaVirtualMachines/openjdk-21.jdk # -- -v 24 -- $ brew install openjdk $ sudo ln -sfn "$(brew --prefix openjdk)/libexec/openjdk.jdk" /Library/Java/JavaVirtualMachines/openjdk-24.jdk # check $ /usr/libexec/java_home -V
$ cp /usr/local/opt/groovy/libexec/lib/groovy.icns groovyConsole.app/Contents/Resources
$ cat > groovyConsole.app/Contents/MacOS/groovyConsole << EOF
#!/usr/bin/env bash
JAVA_HOME="$(/usr/libexec/java_home -v 21)"
GROOVY_HOME="$(/usr/local/bin/brew --prefix groovy)/libexec"
GROOVY_VERSION="$(/usr/bin/sed -rn 's/^[^:]+:[[:blank:]]?([[:digit:].]+)[[:blank:]]?.+$/\1/p' < <(${GROOVY_HOME}/bin/groovy --version))"
"${JAVA_HOME}"/bin/java \
-Dsun.awt.keepWorkingSetOnMinimize=true \
-Xdock:name=GroovyConsole \
-Xdock:icon="${GROOVY_HOME}"/lib/groovy.icns \
-classpath "${GROOVY_HOME}"/lib/groovy-"${GROOVY_VERSION}".jar \
-Dscript.name="${GROOVY_HOME}"/bin/groovyConsole \
-Dprogram.name=groovyConsole \
-Dgroovy.starter.conf="${GROOVY_HOME}"/conf/groovy-starter.conf \
-Dgroovy.home="${GROOVY_HOME}" \
-Dtools.jar="${JAVA_HOME}"/lib/tools.jar org.codehaus.groovy.tools.GroovyStarter \
--main groovy.console.ui.Console \
--conf "${GROOVY_HOME}"/conf/groovy-starter.conf \
--classpath .:"${JAVA_HOME}"/lib/tools.jar:"${JAVA_HOME}"/lib/dt.jar:"${GROOVY_HOME}"/lib
EOF
# or HOMEBREW_PREFIX='/opt/homebrew'
#!/usr/bin/env bash
HOMEBREW_PREFIX='/opt/homebrew'
JAVA_HOME="${HOMEBREW_PREFIX}"/opt/openjdk
export JAVA_HOME
GROOVY_HOME="$("${HOMEBREW_PREFIX}"/bin/brew --prefix groovy)/libexec"
GROOVY_VERSION="$(/usr/bin/sed -rn 's/^[^:]+:[[:blank:]]?([[:digit:].]+)[[:blank:]]?.+$/\1/p' < <("${GROOVY_HOME}"/bin/groovy --version))"
"${JAVA_HOME}"/bin/java \
-Dsun.awt.keepWorkingSetOnMinimize=true \
-Xdock:name=GroovyConsole \
-Xdock:icon="${GROOVY_HOME}"/lib/groovy.icns \
-classpath "${GROOVY_HOME}"/lib/groovy-"${GROOVY_VERSION}".jar \
-Dscript.name="${GROOVY_HOME}"/bin/groovyConsole \
-Dprogram.name=groovyConsole \
-Dgroovy.starter.conf="${GROOVY_HOME}"/conf/groovy-starter.conf \
-Dgroovy.home="${GROOVY_HOME}" \
-Dtools.jar="${JAVA_HOME}"/lib/tools.jar org.codehaus.groovy.tools.GroovyStarter \
--main groovy.console.ui.Console \
--conf "${GROOVY_HOME}"/conf/groovy-starter.conf \
--classpath .:"${JAVA_HOME}"/lib/tools.jar:"${JAVA_HOME}"/lib/dt.jar:"${GROOVY_HOME}"/lib:.
# vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh:
$ chmod +x groovyConsole.app/Contents/MacOS/groovyConsole
$ ls -1 groovyConsole.app/Contents/MacOS/
Automator Application Stub # ignore it
groovyConsole # ╮ <key>CFBundleExecutable</key>
# ╯ <string>groovyConsole</string>
$ mv groovyConsole.app/ /Applications/
$ touch groovyConsole.app/Contents/MacOS/groovyConsole
$ cat > groovyConsole.app/Contents/MacOS/groovyConsole << EOF
-> #!/usr/bin/env bash
->
-> JAVA_HOME="$(/usr/local/bin/brew --prefix java)"
-> # JAVA_HOME="$(/usr/local/bin/brew --prefix openjdk@17)"
-> GROOVY_VERSION="$(/usr/local/bin/groovy --version | /usr/local/opt/gnu-sed/libexec/gnubin/sed -rn 's/^[^:]+:\s*([0-9\.]+).*$/\1/p')"
-> GROOVY_HOME="$(/usr/local/bin/brew --prefix groovy)/libexec"
->
-> "${JAVA_HOME}"/bin/java \
-> -Dsun.awt.keepWorkingSetOnMinimize=true \
-> -Xdock:name=GroovyConsole \
-> -Xdock:icon="${GROOVY_HOME}"/lib/groovy.icns \
-> -classpath "${GROOVY_HOME}"/lib/groovy-"${GROOVY_VERSION}".jar \
-> -Dscript.name="${GROOVY_HOME}"/bin/groovyConsole \
-> -Dprogram.name=groovyConsole \
-> -Dgroovy.starter.conf="${GROOVY_HOME}"/conf/groovy-starter.conf \
-> -Dgroovy.home="${GROOVY_HOME}" \
-> -Dtools.jar="${JAVA_HOME}"/lib/tools.jar \
-> org.codehaus.groovy.tools.GroovyStarter \
-> --main groovy.console.ui.Console \
-> --conf "${GROOVY_HOME}"/conf/groovy-starter.conf \
-> --classpath .:"${JAVA_HOME}"/lib/tools.jar:"${JAVA_HOME}"/lib/dt.jar:"${GROOVY_HOME}"/lib:.
-> EOF
# or
$ cat > groovyConsole.app/Contents/MacOS/groovyConsole << EOF
-> #!/bin/bash
-> /Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java \\
-> -Xdock:name=GroovyConsole \\
-> -Xdock:icon=/usr/local/opt/groovy/libexec/lib/groovy.icns \\
-> -Dgroovy.jaxb=jaxb \\
-> -classpath /usr/local/opt/groovy/libexec/lib/groovy-3.0.6.jar \\
-> -Dscript.name=/usr/local/opt/groovy/libexec/bin/groovyConsole \\
-> -Dprogram.name=groovyConsole \\
-> -Dgroovy.starter.conf=/usr/local/opt/groovy/libexec/conf/groovy-starter.conf \\
-> -Dgroovy.home=/usr/local/opt/groovy/libexec \\
-> -Dtools.jar=/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar org.codehaus.groovy.tools.GroovyStarter \\
-> --main groovy.console.ui.Console \\
-> --conf /usr/local/opt/groovy/libexec/conf/groovy-starter.conf \\
-> --classpath .:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/dt.jar:/usr/local/opt/groovy/libexec/lib:.
-> EOF
$ chmod +x groovyConsole.app/Contents/MacOS/groovyConsole
try validate via execute groovyConsole.app/Contents/MacOS/groovyConsole directly. to see whether if the groovyConsole will be opened.

modify Info.plist
[!NOTE|label:original]
<key>CFBundleExecutable</key> <string>Application Stub</string> <key>CFBundleIconFile</key> <string>AutomatorApplet</string> <key>CFBundleIdentifier</key> <string>com.apple.automator.groovyConsole</string>
$ vim groovyConsole.app/Contents/Info.plist
...
<key>CFBundleExecutable</key>
<string>groovyConsole</string> « the script name to MacOS/groovyConsole
<key>CFBundleIconFile</key>
<string>groovy</string> « for icon in Resources/groovy.icns
<key>CFBundleIdentifier</key>
<string>com.apple.groovyConsole</string>
...
additional
set the icon for new app
[!NOTE|label:optional]
$ cp /usr/local/opt/groovy/libexec/lib/groovy.icns groovyConsole.app/Contents/Resources # or $ ln -sf /usr/local/opt/groovy/libexec/lib/groovy.icns groovyConsole.app/Contents/Resources/groovy.icnscreate dmg
$ hdiutil create -volname 'groovyConsole' \ -srcfolder ~/Desktop/groovyConsole.app \ -ov groovyConsole.dmg ....................... created: /Users/marslo/Desktop/groovyConsole.dmg
python3 IDLE
[!NOTE|label:references:]
python-tk@versionis necessary forIDLEto work$ brew install python-tk@3.11 $ brew install python-tk@3.12 $ brew install python-tk@3.13 $ brew install python-tk@3.14
via automator.app
script
[!TIP|label:tips:]
- the IDLE python version is based on which python is linked to
/usr/local/bin/python3
#!/usr/bin/env bash set -euo pipefail die() { printf >&2 ">> ERROR [IDLE] %s\n" "$*"; exit 1; } HOMEBREW_PREFIX="/opt/homebrew" PYTHON_SHORT_VERSION=$(/usr/bin/sed -rn 's/^([^[0-9]+)([0-9]+\.[0-9]+).*$/\2/p' < <("${HOMEBREW_PREFIX}"/bin/python3 --version) ) PYTHON_TK_HOME="${HOMEBREW_PREFIX}/opt/python-tk@${PYTHON_SHORT_VERSION}" TCLTK_HOME="${HOMEBREW_PREFIX}/opt/tcl-tk" test -d "${PYTHON_TK_HOME}" || die "The python-tk@${PYTHON_SHORT_VERSION} formula is not installed. Please install it with '\$ brew install python-tk@${PYTHON_SHORT_VERSION}' and try again." test -f "${TCLTK_HOME}/lib/pkgconfig/tk.pc" || die "The tcl-tk formula is not installed. Please install it with '\$ brew install tcl-tk' and try again." command -v "${TCLTK_HOME}/bin/wish" || die "wish not found under ${TCLTK_PREFIX}/bin, please check your tcl-tk installation." /usr/bin/open "$("${HOMEBREW_PREFIX}"/bin/brew --prefix python@"${PYTHON_SHORT_VERSION}")"/IDLE\ 3.app # vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh
1.8.1.4.5 -- script in automator.app - the IDLE python version is based on which python is linked to
icon
$ PYTHON_SHORT_VERSION=$(/usr/bin/sed -rn 's/^([^[0-9]+)([0-9]+\.[0-9]+).*$/\2/p' < <("${HOMEBREW_PREFIX}"/bin/python3 --version) ) $ cp "$("${HOMEBREW_PREFIX}"/bin/brew --prefix python@${PYTHON_SHORT_VERSION})"/IDLE\ 3.app/Contents/Resources/IDLE.icns Python3\ IDLE.app/Contents/Resources/ # modify "Python3 IDLE.app/Contents/Info.plist" $ PLIST="Python3 IDLE.app/Contents/Info.plist" $ /usr/libexec/PlistBuddy -c 'Delete :CFBundleIconName' "${PLIST}" 2>/dev/null || true $ /usr/libexec/PlistBuddy -c 'Set :CFBundleIconFile IDLE' "${PLIST}" # -- verify -- $ /usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "${PLIST}" 2>/dev/null IDLE $ plutil -p "${PLIST}" | rg 'CFBundleIcon(File|Name)' "CFBundleIconFile" => "IDLE" # -- result -- <key>CFBundleIconFile</key> <string>IDLE</string> # -- original -- <key>CFBundleIconFile</key> <string>ApplicationStub</string> # refresh icon cache $ /usr/bin/touch Python3\ IDLE.app# others $ cat IDLE.app/Contents/Info.plist <key>CFBundleGetInfoString</key> <string>3.11.6, © 2001-2023 Python Software Foundation</string> <key>CFBundleIconFile</key> <string>IDLE.icns</string> <key>CFBundleIdentifier</key> <string>org.python.IDLE</string>
via appify
[!NOTE|label:references:]
shell script
$ cat > ~/IDLE << EOF #!/usr/bin/env bash set -euo pipefail die() { printf >&2 ">> ERROR [IDLE] %s\n" "$*"; exit 1; } HOMEBREW_PREFIX="/opt/homebrew" PYTHON_SHORT_VERSION=$(/usr/bin/sed -rn 's/^([^[0-9]+)([0-9]+\.[0-9]+).*$/\2/p' < <("${HOMEBREW_PREFIX}"/bin/python3 --version) ) PYTHON_TK_HOME="${HOMEBREW_PREFIX}/opt/python-tk@${PYTHON_SHORT_VERSION}" TCLTK_HOME="${HOMEBREW_PREFIX}/opt/tcl-tk" test -d "${PYTHON_TK_HOME}" || die "The python-tk@${PYTHON_SHORT_VERSION} formula is not installed. Please install it with '\$ brew install python-tk@${PYTHON_SHORT_VERSION}' and try again." test -f "${TCLTK_HOME}/lib/pkgconfig/tk.pc" || die "The tcl-tk formula is not installed. Please install it with '\$ brew install tcl-tk' and try again." command -v "${TCLTK_HOME}/bin/wish" || die "wish not found under ${TCLTK_PREFIX}/bin, please check your tcl-tk installation." /usr/bin/open "$("${HOMEBREW_PREFIX}"/bin/brew --prefix python@"${PYTHON_SHORT_VERSION}")"/IDLE\ 3.app # vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh EOFcreate app via appify
$ icon=$(brew --prefix python@3.12)/IDLE\ 3.app/Contents/Resources/IDLE.icns $ ./appify.sh -i ${icon} -s IDLE -n IDLE $ mv IDLE.app /Applications
1.8.1.4.6 -- IDLE.app more: Info.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> <string>IDLE</string> <key>CFBundleIconFile</key> <string>IDLE</string> <key>CFBundleIdentifier</key> <string>com.apple.automator.Python3-IDLE</string> <key>CFBundleName</key> <string>Python3 IDLE</string> <key>CFBundlePackageType</key> <string>APPL</string> </dict> </plist><?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleExecutable</key> <string>IDLE</string> <key>CFBundleGetInfoString</key> <string>IDLE</string> <key>CFBundleIconFile</key> <string>IDLE</string> <key>CFBundleName</key> <string>IDLE</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleIdentifier</key> <string>org.python.IDLE</string> </dict> </plist>
create dmg
$ hdiutil create -volname IDLE -srcfolder ~/Desktop/IDLE.app -ov IDLE.dmg
....
created: /Users/marslo/Desktop/IDLE.dmg
# -- or --
$ hdiutil create -volname 'Python3 IDLE' -srcfolder "$HOME/Desktop/Python3 IDLE.app" -ov "Python3 IDLE.dmg"
# change default python3
$ ln -sf /usr/local/bin/python3.12 /usr/local/bin/python3
$ ln -sf /usr/local/bin/python3.12-config /usr/local/bin/python3-config
# or
$ brew unlink python@3.11
$ brew unlink python@3.12
$ brew link --force python@3.12
# or
$ brew link --force --overwrite python@3.12
show app info
version
# mdls
$ mdls -name kMDItemVersion /Applications/iTerm.app
kMDItemVersion = "3.6.10"
$ mdls -name kMDItemVersion ~/Applications/iTerm.app
kMDItemVersion = "3.7.20260512-nightly"
# CFBundleShortVersionString
$ defaults read ~/Applications/iTerm.app/Contents/Info.plist CFBundleShortVersionString
3.7.20260512-nightly
$ defaults read /Applications/iTerm.app/Contents/Info.plist CFBundleShortVersionString
3.6.10
# lsappinfo - current running app
$ lsappinfo info com.googlecode.iterm2
"iTerm2" ASN:0x0-0xa30a3: (in front)
bundleID="com.googlecode.iterm2"
bundle path="/Users/marslo/Applications/iTerm.app"
executable path="/Users/marslo/Applications/iTerm.app/Contents/MacOS/iTerm2"
pid = 5064 type="Foreground" flavor=3 Version="3.7.20260511-nightly" fileType="APPL" creator="ITRM" Arch=ARM64
childASNs: ASN:0x0-0xa90a9:
coalition: 1682
parentASN="Alfred" ASN:0x0-0x84084:
launch time = 2026/05/12 15:10:18 ( 2 hours, 50 minutes, 38.7915 seconds ago )
checkin time = 2026/05/12 15:10:18 ( 2 hours, 50 minutes, 38.4025 seconds ago )
launch to checkin time: 0.389042 seconds
# osascript - current
$ osascript -e 'get version of application id "com.googlecode.iterm2"'
3.7.20260512-nightly
check appstore version
app="$HOME/Applications/Bob.app"
bundleId=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "${app}/Contents/Info.plist")
trackId=$(curl -s "https://itunes.apple.com/lookup?bundleId=${bundleId}&country=cn" | jq -r '.results[0].trackId')
latestVersion=$(curl -s "https://itunes.apple.com/lookup?id=${trackId}&country=cn" | jq -r '.results[0].version')
# or search via bundleId
trackId=$(curl -s "https://itunes.apple.com/lookup?bundleId=${bundleId}&country=cn" | jq -r '.results[] | select(.kind=="mac-software") | .version')
app='/Applications/iShot Pro.app'
bundleId=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "${app}/Contents/Info.plist")
local="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "${app}/Contents/Info.plist" 2>/dev/null)"
online="$(curl -fsG 'https://itunes.apple.com/lookup' --data-urlencode "bundleId=${bundleId}" --data-urlencode 'country=cn' | plutil -extract results.0.version raw -o - - )"
echo "${bundleId}: local=${local} online=${online}"
# cn.better365.iShotPro: local=2.6.8 online=2.6.8
fix false alarm
$ db="${HOME}/Library/Caches/com.apple.appstoreagent/storeSystem.db"
$ sqlite3 "${db}" "SELECT bundle_id, update_state, store_software_version_id FROM mapi_app_update;"
# ╭───────────────────────┬──────────────┬──────────────────────╮
# │ bundle_id │ update_state │ store_software_ve... │
# ╞═══════════════════════╪══════════════╪══════════════════════╡
# │ cn.better365.iShotPro │ 1 │ 888549430 │
# │ com.moleskine.overlap │ 0 │ 889377307 │
# ╰───────────────────────┴──────────────┴──────────────────────╯
# or
$ mapfile -t staleIds < <( sqlite3 "${db}" "SELECT bundle_id FROM mapi_app_update WHERE update_state=1;" )
$ printf "%s\n" "${staleIds[@]}"
cn.better365.iShotPro
$ bundleId='cn.better365.iShotPro'
$ db="$HOME/Library/Caches/com.apple.appstoreagent/storeSystem.db"
$ killall appstored appstoreagent 2>/dev/null
# ── force reset with bundle_id ──
$ sqlite3 "UPDATE mapi_app_update SET update_state=0 WHERE bundle_id='${bundleId}';"
# ── force reset all ──
$ for bundleId in "${staleIds[@]}"; do
sqlite3 "${db}" "UPDATE mapi_app_update SET update_state=0 WHERE bundle_id='${bundleId}';"
done
# clean the badge count
$ defaults write com.apple.appstored BadgeCount -int 0
$ defaults delete com.apple.appstored BadgeCount 2>/dev/null
killall appstored appstoreagent 2>/dev/null
input method auto switch
im-select
$ brew tap daipeihust/tap
$ brew install im-select
# or
$ curl -Ls https://raw.githubusercontent.com/daipeihust/im-select/master/install_mac.sh | sh
# or compile from source
$ git clone https://github.com/laishulu/macism /tmp/macism
$ cd /tmp/macism && swiftc macism.swift -o macism
$ sudo mv macism /usr/local/bin/
macime
$ brew tap riodelphino/tap
$ brew install macime
# list all input method
$ macime list
com.apple.keylayout.US
com.apple.CharacterPaletteIM
com.apple.inputmethod.ironwood
com.sogou.inputmethod.sogou.pinyin
com.sogou.inputmethod.sogou
# get current input method
$ macime get
com.sogou.inputmethod.sogou.pinyin
# switch input method
$ macime set com.apple.keylayout.US
" autocmd for force change input method
if executable('macime')
let g:ime_en = 'com.apple.keylayout.US'
augroup Ime_Switch
autocmd!
autocmd FocusGained * call system( 'macime set ' . g:ime_en )
autocmd InsertLeave * call system( 'macime set ' . g:ime_en )
autocmd CmdlineLeave * call system( 'macime set ' . g:ime_en )
augroup END
endif
" --- or ---
if executable('macime')
let g:ime_en = 'com.apple.keylayout.US'
augroup Ime_Switch
autocmd!
autocmd WinEnter * silent! call system( 'macime set ' . g:ime_en . ' &>/dev/null &' )
autocmd InsertLeave * silent! call system( 'macime set ' . g:ime_en . ' &>/dev/null &' )
autocmd CmdlineLeave * silent! call system( 'macime set ' . g:ime_en . ' &>/dev/null &' )
augroup END
endif
macism
$ brew tap laishulu/homebrew
$ brew install macism
switch input method in cursor/vscode
local MACIME = "/opt/homebrew/bin/macime"
local ENGLISH = "com.apple.keylayout.US"
-- brew tap riodelphino/tap && brew install macime
local function switchToEnglish()
hs.execute(MACIME .. " set " .. ENGLISH)
end
local function getFocusedDescription(app)
local el = hs.axuielement.applicationElement(app):attributeValue("AXFocusedUIElement")
if not el then return nil end
return el:attributeValue("AXRoleDescription")
end
-- watch focus changes within Cursor
local observer
local function startObserver()
local app = hs.application.get("Cursor")
if not app then return end
local axApp = hs.axuielement.applicationElement(app)
observer = hs.axuielement.observer.new(app:pid())
observer:addWatcher(axApp, "AXFocusedUIElementChanged")
observer:callback(function()
local a = hs.application.get("Cursor")
if not a then return end
local desc = getFocusedDescription(a)
if desc == "editor" then
switchToEnglish()
end
end)
observer:start()
end
-- start observer when Cursor launches or is activated
hs.application.watcher.new(function(name, event, _)
if name ~= "Cursor" then return end
if event == hs.application.watcher.launched
or event == hs.application.watcher.activated then
startObserver()
end
if event == hs.application.watcher.terminated then
if observer then observer:stop(); observer = nil end
end
end):start()
-- handle already-running Cursor
startObserver()
others
notch
[!TIP|label:references:]
hammerspoon
[!NOTE|label:references:]
to show debug info
-- ~/.hammerspoon/init.lua
local function logFocused()
local app = hs.application.get("Cursor")
if not app then print("Cursor not running"); return end
local focused = hs.axuielement.applicationElement(app):attributeValue("AXFocusedUIElement")
if not focused then print("no focused element"); return end
print("=== focused element ===")
print("role: " .. (focused:attributeValue("AXRole") or "nil"))
print("subrole: " .. (focused:attributeValue("AXSubrole") or "nil"))
print("description: " .. (focused:attributeValue("AXRoleDescription") or "nil"))
print("title: " .. (focused:attributeValue("AXTitle") or "nil"))
print("identifier: " .. (focused:attributeValue("AXIdentifier") or "nil"))
print("label: " .. (focused:attributeValue("AXLabel") or "nil"))
print("=======================")
end
-- ctrl + F1: log focused element
hs.hotkey.bind({"ctrl"}, "f1", logFocused)