Skip to content

Instantly share code, notes, and snippets.

@realdimas
Last active April 13, 2025 08:35
Show Gist options
  • Save realdimas/e58723564cfada8efd93adab6efb747c to your computer and use it in GitHub Desktop.
Save realdimas/e58723564cfada8efd93adab6efb747c to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# set -e: exit on command failure
# set -u: error on using undefined variables
# set -o pipefail: fail a pipeline if any command within it fails
set -euo pipefail
# ---------------------------------------------------------------------------
# CLI script that updates or reads a value from a JSON file using jq.
# By default, it targets /Applications/Cursor.app/Contents/Resources/app/product.json,
# but you can override that via --file=... or -f.
#
# Inspired by:
# https://gist.github.com/joeblackwaslike/752b26ce92e3699084e1ecfc790f74b2
#
# Usage examples:
# ./cursor-fixup.sh
# ./cursor-fixup.sh -n
# ./cursor-fixup.sh '.extensionMaxVersions."ms-vscode-remote.remote-containers".maxVersion'
# ./cursor-fixup.sh '.extensionMaxVersions."ms-vscode-remote.remote-containers".maxVersion' 0.399.0
# ---------------------------------------------------------------------------
APP_PATH="/Applications/Cursor.app"
TARGET_FILE="${APP_PATH}/Contents/Resources/app/product.json"
DRY_RUN=false
# Store the script name for usage
SCRIPT_NAME=$(basename "$0")
# Helper function for consistent logging
log_message() {
local status="$1"
local message="$2"
echo "[${status}] ${message}"
}
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} [options] [command or json_path [args...]]
Options:
--file, -f <path> Override the default JSON file location (default: $TARGET_FILE)
--dry-run, -n Show what would be changed, but do not actually write
--help, -h Show this help message
Commands:
check_and_resign [app_path] Check and re-sign the specified application if needed
(default: $APP_PATH)
remove_quarantine [app_path] Remove quarantine attributes from the specified application
(default: $APP_PATH)
JSON Operations:
<json_path> Get the value at the JSON path
<json_path> <new_value> Update the value at the JSON path
If no arguments given, the script applies a predefined set of changes
to the default file (or a file you specify).
Examples:
# Perform the default updates on the standard file
${SCRIPT_NAME}
# Dry-run only (no changes written)
${SCRIPT_NAME} -n
# Print the current value
${SCRIPT_NAME} '.extensionMaxVersions."ms-vscode-remote.remote-containers".maxVersion'
# Update that same key
${SCRIPT_NAME} '.extensionMaxVersions."ms-vscode-remote.remote-containers".maxVersion' 0.399.0
# Check and re-sign an application (using default path)
${SCRIPT_NAME} check_and_resign
# Check and re-sign a specific application
${SCRIPT_NAME} check_and_resign /Applications/Cursor.app
# Remove quarantine attribute (using default path)
${SCRIPT_NAME} remove_quarantine
# Remove quarantine attribute for a specific application
${SCRIPT_NAME} remove_quarantine /Applications/Cursor.app
EOF
}
# Ensures the path starts with a leading dot if it doesn't already.
ensure_leading_dot() {
local path="$1"
if [ -z "$path" ]; then
echo "$path"
elif [[ "$path" == .* ]]; then
echo "$path"
else
echo ".$path"
fi
}
# Prints the current value of 'json_path' from 'file' using jq.
read_json_value() {
local file="$1"
local raw_path="$2"
local path_to_use
path_to_use="$(ensure_leading_dot "$raw_path")"
if ! jq -e "$path_to_use" "$file" &>/dev/null; then
log_message "NO_KEY" "${path_to_use}: key does not exist or cannot be retrieved."
return 0
fi
local val
val="$(jq -r "$path_to_use" "$file")"
log_message "READ" "${path_to_use} => ${val}"
}
# Updates 'json_path' in 'file' to 'new_value' using jq.
# Uses a herestring to feed the new JSON into the original file.
update_json_value() {
local file="$1"
local raw_path="$2"
local new_value="$3"
local path_to_use
path_to_use="$(ensure_leading_dot "$raw_path")"
local key_missing=false
if ! jq -e "$path_to_use" "$file" &>/dev/null; then
key_missing=true
fi
local current_value
current_value="$(jq -r "$path_to_use" "$file")"
if [ "$current_value" = "$new_value" ]; then
log_message "NO_CHANGE" "${path_to_use}: Already => ${new_value}"
return 0
fi
if [ "$DRY_RUN" = true ]; then
if [ "$key_missing" = true ]; then
log_message "WILL_CREATE" "${path_to_use}: Would create key and set to => ${new_value}"
else
log_message "WILL_UPDATE" "${path_to_use}: Would change from => ${current_value} to => ${new_value}"
fi
return 0
fi
# Generate new JSON content using jq (in a variable) so we can check if it failed
local jq_out
if ! jq_out="$(jq --arg val "$new_value" "$path_to_use |= \$val" "$file" 2>/dev/null)"; then
log_message "ERROR" "jq failed trying to update ${path_to_use}"
return 1
fi
if [ -z "$jq_out" ]; then
log_message "ERROR" "jq produced no output for updating ${path_to_use}"
return 1
fi
printf "%s\n" "$jq_out" >"$file"
if [ "$key_missing" = true ]; then
log_message "CREATED" "${path_to_use}: Set to => ${new_value}"
else
log_message "UPDATED" "${path_to_use}: Changed from => ${current_value} to => ${new_value}"
fi
}
check_and_resign() {
local APP_PATH="$1"
local STATUS=0
local ERROR_MSG
ERROR_MSG="$(codesign -v --deep --strict "${APP_PATH}" 2>&1)" || STATUS=$?
if [ "$STATUS" -eq 1 ] && [[ "$ERROR_MSG" == *"a sealed resource is missing or invalid"* ]]; then
if [ "$DRY_RUN" = true ]; then
log_message "WILL_RESIGN" "${APP_PATH}: Resource is missing or invalid, would re-sign"
return 0
fi
local codesign_output
codesign_output=$(codesign --force --deep --sign - "${APP_PATH}" 2>&1)
log_message "RESIGNED" "${APP_PATH}: ${codesign_output}"
else
log_message "NO_CHANGE" "${APP_PATH}: No code signature issues found"
fi
}
remove_quarantine() {
local APP_PATH="$1"
# Check for quarantine attribute. The presence of "com.apple.quarantine"
# in `xattr` output indicates it may be set.
if xattr -p com.apple.quarantine "${APP_PATH}" &>/dev/null; then
if [ "$DRY_RUN" = true ]; then
log_message "WILL_UPDATE" "${APP_PATH}: Would remove quarantine attributes"
return 0
fi
log_message "UPDATING" "${APP_PATH}: Removing quarantine attributes"
xattr -r -d com.apple.quarantine "${APP_PATH}"
log_message "UPDATED" "${APP_PATH}: Quarantine attributes removed"
else
log_message "NO_CHANGE" "${APP_PATH}: No quarantine attributes found"
fi
}
# Apply predefined JSON updates
apply_predefined_updates() {
log_message "INFO" "Using file => $TARGET_FILE"
log_message "INFO" "Applying predefined JSON updates..."
# Switchover to MS VSCode Marketplace endpoints
# galleryId needs to be unchanged to avoid "Error while fetching extensions. Cannot read properties of undefined (reading 'identifier')" exception
# update_json_value "$TARGET_FILE" .extensionsGallery.galleryId cursor
update_json_value "$TARGET_FILE" .extensionsGallery.serviceUrl https://marketplace.visualstudio.com/_apis/public/gallery
update_json_value "$TARGET_FILE" .extensionsGallery.itemUrl https://marketplace.visualstudio.com/items
update_json_value "$TARGET_FILE" .extensionsGallery.resourceUrlTemplate https://{publisher}.vscode-unpkg.net/{publisher}/{name}/{version}/{path}
update_json_value "$TARGET_FILE" .extensionsGallery.controlUrl https://main.vscode-cdn.net/extensions/marketplace.json
update_json_value "$TARGET_FILE" .extensionsGallery.nlsBaseUrl https://www.vscode-unpkg.net/_lp/
update_json_value "$TARGET_FILE" .extensionsGallery.publisherUrl https://marketplace.visualstudio.com/publishers
# Compatibility as of Cursor Version: 0.48.7 (VSCode Version: 1.96.2) on macOS (arm64)
#
# ms-python.python
# Marketplace URL: https://marketplace.visualstudio.com/items?itemName=ms-python.python
# VSIX download URL (darwin-arm64): https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/python/2025.5.2025040401/vspackage?targetPlatform=darwin-arm64
# VSIX download URL (linux-arm64): https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/python/2025.5.2025040401/vspackage?targetPlatform=linux-arm64
# VSIX download URL (linux-x64): https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/python/2025.5.2025040401/vspackage?targetPlatform=linux-x64
# Last working (via .vsix) version: 2025.5.2025040401 (current)
update_json_value "$TARGET_FILE" '.extensionMaxVersions."ms-python.python".maxVersion' 2025.5.2025040401
#
# ms-python.debugpy
# Marketplace URL: https://marketplace.visualstudio.com/items?itemName=ms-python.debugpy
# VSIX download URL (darwin-arm64): https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/debugpy/2025.6.0/vspackage?targetPlatform=darwin-arm64
# VSIX download URL (linux-arm64): https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/debugpy/2025.6.0/vspackage?targetPlatform=linux-arm64
# VSIX download URL (linux-x64): https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/debugpy/2025.6.0/vspackage?targetPlatform=linux-x64
# Last working (via .vsix) version: 2025.6.0 (current)
update_json_value "$TARGET_FILE" '.extensionMaxVersions."ms-python.debugpy".maxVersion' 2025.6.0
#
# ms-python.vscode-pylance
# Marketplace URL: https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance
# VSIX download URL: https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/vscode-pylance/2025.4.1/vspackage
# Last working (via .vsix) version: 2025.4.1 (current)
update_json_value "$TARGET_FILE" '.extensionMaxVersions."ms-python.vscode-pylance".maxVersion' 2025.4.1
#
# ms-vscode-remote.remote-containers
# Marketplace URL: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
# VSIX download URL: https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-vscode-remote/vsextensions/remote-containers/0.399.0/vspackage
# Last working (via .vsix) version: 0.399.0 (versions up to and including 0.410.0 are not installable)
update_json_value "$TARGET_FILE" '.extensionMaxVersions."ms-vscode-remote.remote-containers".maxVersion' 0.399.0
#
# anysphere.pyright
# Suppress installation by setting maxVersion to 0.0.0-0
# VSIX download URL: https://open-vsx.org/api/anysphere/pyright/1.1.327/file/anysphere.pyright-1.1.327.vsix
update_json_value "$TARGET_FILE" '.extensionMaxVersions."anysphere.pyright".maxVersion' 0.0.0-0
# resign if needed (this would invalidate existing keychain access grants prompting for manual confirmation)
check_and_resign "${APP_PATH}"
# remove quarantine attribute if needed
remove_quarantine "${APP_PATH}"
log_message "INFO" "Successfully completed all updates."
}
# Parse flags and any remaining positional arguments
POSITIONAL=()
while [ $# -gt 0 ]; do
case "$1" in
--file | -f)
TARGET_FILE="$2"
shift 2
;;
--dry-run | -n)
DRY_RUN=true
shift
;;
--help | -h)
usage
exit 0
;;
*)
# Treat anything else as a positional argument (json_path or new_value)
POSITIONAL+=("$1")
shift
;;
esac
done
# Restore positional arguments
pos_count=0
if [ ${#POSITIONAL[@]:-0} -gt 0 ]; then
set -- "${POSITIONAL[@]}"
pos_count=$#
else
# Clear arguments when POSITIONAL is empty
set --
pos_count=0
fi
# Ensure jq is installed
if ! command -v jq &>/dev/null; then
log_message "ERROR" "'jq' is required but not installed."
exit 1
fi
# Handle commands based on argument count
if [ "$pos_count" -gt 0 ]; then
# First check if the first argument is a known command
case "$1" in
"check_and_resign")
if [ "$pos_count" -gt 2 ]; then
log_message "ERROR" "check_and_resign takes at most one argument (app path)"
usage
exit 1
fi
# Use the provided app path or default
APP_PATH_ARG="${2:-$APP_PATH}"
check_and_resign "$APP_PATH_ARG"
exit 0
;;
"remove_quarantine")
if [ "$pos_count" -gt 2 ]; then
log_message "ERROR" "remove_quarantine takes at most one argument (app path)"
usage
exit 1
fi
# Use the provided app path or default
APP_PATH_ARG="${2:-$APP_PATH}"
remove_quarantine "$APP_PATH_ARG"
exit 0
;;
*)
# Handle as JSON operations
if [ "$pos_count" -eq 1 ]; then
# "read" mode: show the existing JSON value
read_json_value "$TARGET_FILE" "$1"
exit 0
elif [ "$pos_count" -eq 2 ]; then
# "update" mode: update the JSON with the new value
update_json_value "$TARGET_FILE" "$1" "$2"
exit 0
fi
;;
esac
fi
# If no commands were handled above, apply the predefined updates
apply_predefined_updates
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment