@supercode-sh/claude-install-mac 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -11
- package/dist/assets/bootstrap/index-legacy.cjs +9 -0
- package/dist/assets/bootstrap/index.cjs +9 -0
- package/dist/assets/bootstrap/package.json +1 -0
- package/dist/assets/runtime/main.cjs +1 -0
- package/dist/assets/runtime/managed-skills-worker.cjs +1 -0
- package/dist/assets/runtime/mcp-stdio.zsh +9 -0
- package/dist/assets/runtime/preload.cjs +1 -0
- package/dist/assets/runtime/recovery/macos/claude-process.zsh +19 -0
- package/dist/assets/runtime/recovery/macos/common.zsh +97 -0
- package/dist/assets/runtime/recovery/macos/manifest.json +21 -0
- package/dist/assets/runtime/recovery/macos/recover.zsh +71 -0
- package/dist/assets/runtime/recovery/macos/server.cjs +1 -0
- package/dist/assets/runtime/renderer.js +3 -0
- package/dist/assets/runtime/setup.js +1 -0
- package/dist/claude-install-mac +5 -0
- package/dist/macos/claude-asar-bootstrap.zsh +416 -0
- package/dist/macos/claude-installation-state.zsh +49 -0
- package/dist/macos/claude-process.zsh +19 -0
- package/dist/macos/claude-recovery-extension.zsh +62 -0
- package/dist/macos/common.zsh +97 -0
- package/dist/macos/install.zsh +122 -0
- package/dist/macos/recover.zsh +71 -0
- package/dist/recovery-extension/manifest.json +21 -0
- package/dist/recovery-extension/server.cjs +8 -0
- package/dist/version +1 -0
- package/package.json +10 -6
- package/bin/noop.mjs +0 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/bin/zsh -f
|
|
2
|
+
|
|
3
|
+
# Installs the recovery trigger into Claude's own local-extension profile. Claude
|
|
4
|
+
# runs the bundled JS patcher in its signed Plugin helper; zsh only coordinates relaunch.
|
|
5
|
+
|
|
6
|
+
sc_configure_recovery_extension() {
|
|
7
|
+
local user_data=${1:A} coordinator_hook=${2:A} extension_source=${3:A} storage_root=${4:A}
|
|
8
|
+
local runtime_version=$5 bootstrap_namespace=$6
|
|
9
|
+
local extension_id=local.supercode-recovery extension_root settings_root target settings_target
|
|
10
|
+
local coordinator_json storage_json runtime_json namespace_json
|
|
11
|
+
local -a existing_names=()
|
|
12
|
+
sc_assert_path "$user_data"
|
|
13
|
+
sc_assert_path "$coordinator_hook"
|
|
14
|
+
sc_assert_path "$extension_source"
|
|
15
|
+
sc_assert_path "$storage_root"
|
|
16
|
+
[[ "$bootstrap_namespace" == .s || "$bootstrap_namespace" == .d || "$bootstrap_namespace" == .e ]] ||
|
|
17
|
+
sc_fail 'Unsupported Claude bootstrap namespace.'
|
|
18
|
+
[[ -n "$runtime_version" ]] || sc_fail 'Claude recovery runtime version is unavailable.'
|
|
19
|
+
[[ -f "$coordinator_hook" && ! -L "$coordinator_hook" ]] || sc_fail 'Claude recovery coordinator is unavailable.'
|
|
20
|
+
[[ -f "$extension_source/manifest.json" && ! -L "$extension_source/manifest.json" ]] ||
|
|
21
|
+
sc_fail 'Claude recovery extension manifest is unavailable.'
|
|
22
|
+
[[ -f "$extension_source/server.cjs" && ! -L "$extension_source/server.cjs" ]] ||
|
|
23
|
+
sc_fail 'Claude recovery extension server is unavailable.'
|
|
24
|
+
|
|
25
|
+
/bin/mkdir -p "$user_data"
|
|
26
|
+
[[ -d "$user_data" && ! -L "$user_data" ]] || sc_fail 'Claude user data directory is invalid.'
|
|
27
|
+
extension_root="$user_data/Claude Extensions"
|
|
28
|
+
settings_root="$user_data/Claude Extensions Settings"
|
|
29
|
+
target="$extension_root/$extension_id"
|
|
30
|
+
settings_target="$settings_root/$extension_id.json"
|
|
31
|
+
/bin/mkdir -p "$extension_root" "$settings_root"
|
|
32
|
+
[[ -d "$extension_root" && ! -L "$extension_root" && -d "$settings_root" && ! -L "$settings_root" ]] ||
|
|
33
|
+
sc_fail 'Claude recovery extension directories are invalid.'
|
|
34
|
+
|
|
35
|
+
if [[ -e "$target" || -L "$target" ]]; then
|
|
36
|
+
[[ -d "$target" && ! -L "$target" ]] || sc_fail 'Claude recovery extension location is occupied.'
|
|
37
|
+
existing_names=("$target"/*(DN:t))
|
|
38
|
+
[[ ${(j:,:)${(on)existing_names}} == manifest.json,recovery.json,server.cjs,supercode-owned ]] ||
|
|
39
|
+
sc_fail 'Claude recovery extension location is not owned by Supercode.'
|
|
40
|
+
[[ "$(<"$target/supercode-owned")" == 'supercode-claude-recovery-v1' ]] ||
|
|
41
|
+
sc_fail 'Claude recovery extension ownership marker is invalid.'
|
|
42
|
+
else
|
|
43
|
+
/bin/mkdir -m 700 "$target"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
/bin/cp -p "$extension_source/manifest.json" "$sc_stage/recovery-extension-manifest.json"
|
|
47
|
+
/bin/cp -p "$extension_source/server.cjs" "$sc_stage/recovery-extension-server.cjs"
|
|
48
|
+
print -rn -- 'supercode-claude-recovery-v1' >"$sc_stage/recovery-extension-owned"
|
|
49
|
+
sc_json_string "$coordinator_hook"; coordinator_json=$REPLY
|
|
50
|
+
sc_json_string "$storage_root"; storage_json=$REPLY
|
|
51
|
+
sc_json_string "$runtime_version"; runtime_json=$REPLY
|
|
52
|
+
sc_json_string "$bootstrap_namespace"; namespace_json=$REPLY
|
|
53
|
+
print -r -- "{\"schemaVersion\":1,\"coordinatorHook\":$coordinator_json,\"storageRoot\":$storage_json,\"runtimeVersion\":$runtime_json,\"bootstrapNamespace\":$namespace_json}" \
|
|
54
|
+
>"$sc_stage/recovery-extension-config.json"
|
|
55
|
+
print -r -- '{"isEnabled":true}' >"$sc_stage/recovery-extension-settings.json"
|
|
56
|
+
|
|
57
|
+
sc_copy_atomic "$sc_stage/recovery-extension-manifest.json" "$target/manifest.json"
|
|
58
|
+
sc_copy_atomic "$sc_stage/recovery-extension-server.cjs" "$target/server.cjs"
|
|
59
|
+
sc_copy_atomic "$sc_stage/recovery-extension-config.json" "$target/recovery.json"
|
|
60
|
+
sc_copy_atomic "$sc_stage/recovery-extension-owned" "$target/supercode-owned"
|
|
61
|
+
sc_copy_atomic "$sc_stage/recovery-extension-settings.json" "$settings_target"
|
|
62
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Shared primitives; sourced only by the reviewed install/recovery entrypoints.
|
|
2
|
+
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
|
|
3
|
+
export LC_ALL=C
|
|
4
|
+
umask 077
|
|
5
|
+
|
|
6
|
+
sc_fail() {
|
|
7
|
+
print -ru2 -- "Supercode: $*"
|
|
8
|
+
return 1
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
sc_sha256_file() {
|
|
12
|
+
[[ -f "$1" ]] || sc_fail "Cannot hash a missing file: $1"
|
|
13
|
+
/usr/bin/openssl dgst -sha256 "$1" | /usr/bin/awk '{print $NF}'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
sc_assert_path() {
|
|
17
|
+
[[ "$1" == /* && "$1" != *[[:cntrl:]]* ]] || sc_fail 'An absolute path without control characters is required.'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
sc_json_string() {
|
|
21
|
+
local value=$1
|
|
22
|
+
value=${value//\\/\\\\}
|
|
23
|
+
value=${value//\"/\\\"}
|
|
24
|
+
value=${value//$'\n'/\\n}
|
|
25
|
+
value=${value//$'\r'/\\r}
|
|
26
|
+
value=${value//$'\t'/\\t}
|
|
27
|
+
REPLY="\"$value\""
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
sc_process() {
|
|
31
|
+
sc_verify_process "$sc_app" "$sc_pid"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
sc_acquire_lock() {
|
|
35
|
+
local owner previous owner_pid
|
|
36
|
+
sc_lock="$sc_root/lifecycle.lock"
|
|
37
|
+
owner="$$:$(/usr/bin/uuidgen)"
|
|
38
|
+
if ! /bin/ln -s "$owner" "$sc_lock" 2>/dev/null; then
|
|
39
|
+
[[ -L "$sc_lock" ]] || sc_fail 'Lifecycle lock is occupied.'
|
|
40
|
+
previous=$(/usr/bin/readlink "$sc_lock")
|
|
41
|
+
owner_pid=${previous%%:*}
|
|
42
|
+
[[ "$owner_pid" == <1-> ]] || sc_fail 'Lifecycle lock has invalid ownership.'
|
|
43
|
+
if kill -0 "$owner_pid" 2>/dev/null; then
|
|
44
|
+
sc_fail 'Another install or recovery is running.'
|
|
45
|
+
return 1
|
|
46
|
+
fi
|
|
47
|
+
[[ "$(/usr/bin/readlink "$sc_lock")" == "$previous" ]] || return 1
|
|
48
|
+
/bin/rm "$sc_lock"
|
|
49
|
+
/bin/ln -s "$owner" "$sc_lock" || return 1
|
|
50
|
+
fi
|
|
51
|
+
sc_lock_owner=$owner
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
sc_cleanup() {
|
|
55
|
+
if [[ -n ${sc_lock_owner:-} && -L ${sc_lock:-} && "$(/usr/bin/readlink "$sc_lock")" == "$sc_lock_owner" ]]; then
|
|
56
|
+
/bin/rm "$sc_lock"
|
|
57
|
+
fi
|
|
58
|
+
if [[ -n ${sc_stage:-} && "$sc_stage" == "$sc_root"/install-stage.* && -d "$sc_stage" && ! -L "$sc_stage" ]]; then
|
|
59
|
+
/bin/rm -r -- "$sc_stage"
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
sc_copy_atomic() {
|
|
64
|
+
local temporary
|
|
65
|
+
[[ -f "$1" && ! -L "$1" ]] || sc_fail 'Distribution asset is missing or is a symlink.'
|
|
66
|
+
[[ ! -L "$2" ]] || sc_fail 'Refusing to replace a symlinked state file.'
|
|
67
|
+
temporary=$(/usr/bin/mktemp "${2}.XXXXXXXX") || return 1
|
|
68
|
+
/bin/cp "$1" "$temporary" || { /bin/rm -f "$temporary"; return 1; }
|
|
69
|
+
/bin/chmod "${3:-600}" "$temporary" || { /bin/rm -f "$temporary"; return 1; }
|
|
70
|
+
/bin/mv -f "$temporary" "$2" || { /bin/rm -f "$temporary"; return 1; }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
sc_select_app() {
|
|
74
|
+
local row candidate pid
|
|
75
|
+
local -a matches=()
|
|
76
|
+
if [[ -n "$sc_app" ]]; then
|
|
77
|
+
sc_assert_path "$sc_app"
|
|
78
|
+
sc_app=${sc_app:A}
|
|
79
|
+
fi
|
|
80
|
+
if [[ -z "$sc_app" || -z "$sc_pid" ]]; then
|
|
81
|
+
while IFS= read -r row; do
|
|
82
|
+
row=${row##[[:space:]]#}
|
|
83
|
+
pid=${row%%[[:space:]]*}
|
|
84
|
+
candidate=${row#${pid}}
|
|
85
|
+
candidate=${candidate##[[:space:]]#}
|
|
86
|
+
[[ "$candidate" == */Claude.app/Contents/MacOS/Claude ]] || continue
|
|
87
|
+
[[ -z "$sc_app" || "$candidate" == "$sc_app/Contents/MacOS/Claude" ]] || continue
|
|
88
|
+
[[ -z "$sc_pid" || "$pid" == "$sc_pid" ]] || continue
|
|
89
|
+
matches+=("$pid" "${candidate%/Contents/MacOS/Claude}")
|
|
90
|
+
done < <(/bin/ps -axo pid=,comm=)
|
|
91
|
+
(( ${#matches} == 2 )) || sc_fail 'Open one Claude Desktop, or select its exact --app and --pid.'
|
|
92
|
+
sc_pid=$matches[1]
|
|
93
|
+
sc_app=${matches[2]:A}
|
|
94
|
+
fi
|
|
95
|
+
[[ -d "$sc_app" && "${sc_app:t}" == Claude.app ]] || sc_fail 'Invalid Claude application directory.'
|
|
96
|
+
sc_process
|
|
97
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/bin/zsh -f
|
|
2
|
+
emulate -LR zsh
|
|
3
|
+
setopt ERR_EXIT NO_UNSET PIPE_FAIL EXTENDED_GLOB
|
|
4
|
+
sc_distribution=${0:A:h:h}
|
|
5
|
+
source "$sc_distribution/macos/common.zsh"
|
|
6
|
+
source "$sc_distribution/macos/claude-asar-bootstrap.zsh"
|
|
7
|
+
source "$sc_distribution/macos/claude-installation-state.zsh"
|
|
8
|
+
source "$sc_distribution/macos/claude-process.zsh"
|
|
9
|
+
source "$sc_distribution/macos/claude-recovery-extension.zsh"
|
|
10
|
+
|
|
11
|
+
sc_app='' sc_pid='' sc_user_data='' sc_channel=stable
|
|
12
|
+
sc_root=${SUPERCODE_CLAUDE_STORAGE_ROOT:-$HOME/.supercode/claude}
|
|
13
|
+
sc_bootstrap_namespace=${SUPERCODE_CLAUDE_BOOTSTRAP_NAMESPACE:-.s}
|
|
14
|
+
|
|
15
|
+
sc_publish_external_runtime() {
|
|
16
|
+
local sc_shim="$HOME/$sc_bootstrap_namespace" sc_release="$sc_root/extension/seed/$sc_version"
|
|
17
|
+
local sc_file sc_main_json
|
|
18
|
+
local -a sc_names=()
|
|
19
|
+
if [[ -e "$sc_shim" || -L "$sc_shim" ]]; then
|
|
20
|
+
[[ -d "$sc_shim" && ! -L "$sc_shim" ]] || sc_fail 'The bootstrap location is occupied.'
|
|
21
|
+
sc_names=("$sc_shim"/*(DN:t))
|
|
22
|
+
[[ ${(j:,:)${(on)sc_names}} == index.cjs,package.json ]] || sc_fail 'The bootstrap location is not owned by Supercode.'
|
|
23
|
+
[[ -f "$sc_shim/index.cjs" && ! -L "$sc_shim/index.cjs" && -f "$sc_shim/package.json" && ! -L "$sc_shim/package.json" ]] ||
|
|
24
|
+
sc_fail 'Invalid bootstrap file.'
|
|
25
|
+
/usr/bin/cmp -s "$sc_shim/package.json" "$sc_distribution/assets/bootstrap/package.json" ||
|
|
26
|
+
sc_fail 'Unrelated bootstrap files; refusing to overwrite.'
|
|
27
|
+
if ! /usr/bin/cmp -s "$sc_shim/index.cjs" "$sc_distribution/assets/bootstrap/index.cjs"; then
|
|
28
|
+
/usr/bin/cmp -s "$sc_shim/index.cjs" "$sc_distribution/assets/bootstrap/index-legacy.cjs" ||
|
|
29
|
+
sc_fail 'Unrelated bootstrap files; refusing to overwrite.'
|
|
30
|
+
sc_copy_atomic "$sc_distribution/assets/bootstrap/index.cjs" "$sc_shim/index.cjs"
|
|
31
|
+
fi
|
|
32
|
+
else
|
|
33
|
+
/bin/mkdir -m 700 "$sc_shim"
|
|
34
|
+
for sc_file in index.cjs package.json; do
|
|
35
|
+
sc_copy_atomic "$sc_distribution/assets/bootstrap/$sc_file" "$sc_shim/$sc_file"
|
|
36
|
+
done
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
/bin/mkdir -p "$sc_root/extension/seed" "$sc_root/runtime" "$sc_root/logs"
|
|
40
|
+
if [[ ! -e "$sc_release" ]]; then
|
|
41
|
+
/bin/cp -R "$sc_distribution/assets/runtime" "$sc_stage/release"
|
|
42
|
+
/bin/mv "$sc_stage/release" "$sc_release"
|
|
43
|
+
else
|
|
44
|
+
/usr/bin/diff -rq "$sc_distribution/assets/runtime" "$sc_release" >/dev/null ||
|
|
45
|
+
sc_fail 'The installed Claude seed release does not match this installer.'
|
|
46
|
+
fi
|
|
47
|
+
sc_json_string "$sc_release/main.cjs"; sc_main_json=$REPLY
|
|
48
|
+
print -r -- "module.exports=require($sc_main_json);" >"$sc_stage/active.cjs"
|
|
49
|
+
sc_copy_atomic "$sc_stage/active.cjs" "$sc_root/runtime/main.cjs"
|
|
50
|
+
print -r -- "$sc_channel" >"$sc_stage/channel-v1"
|
|
51
|
+
sc_copy_atomic "$sc_stage/channel-v1" "$sc_root/extension/channel-v1"
|
|
52
|
+
print -r -- '#!/bin/zsh -f' >"$sc_stage/recover-v1"
|
|
53
|
+
print -r -- "exec /bin/zsh -f ${(q)sc_release}/recovery/macos/recover.zsh \"\$@\"" >>"$sc_stage/recover-v1"
|
|
54
|
+
sc_copy_atomic "$sc_stage/recover-v1" "$sc_root/recover-v1" 700
|
|
55
|
+
sc_configure_recovery_extension "$sc_user_data" "$sc_root/recover-v1" "$sc_release/recovery/macos" "$sc_root" "$sc_version" \
|
|
56
|
+
"$sc_bootstrap_namespace"
|
|
57
|
+
print -rn -- '' >"$sc_stage/enabled"
|
|
58
|
+
sc_copy_atomic "$sc_stage/enabled" "$sc_root/recovery-enabled-v1"
|
|
59
|
+
# The active selector is the final atomic publication boundary.
|
|
60
|
+
sc_copy_atomic "$sc_stage/active.cjs" "$sc_root/extension/active.cjs"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
[[ "$sc_bootstrap_namespace" == .s || "$sc_bootstrap_namespace" == .d || "$sc_bootstrap_namespace" == .e ]] ||
|
|
64
|
+
sc_fail 'Unsupported Claude bootstrap namespace.'
|
|
65
|
+
while (( $# )); do
|
|
66
|
+
case "$1" in
|
|
67
|
+
--help)
|
|
68
|
+
print -r -- 'Open Claude Desktop, then run claude-install-mac [--app /path/to/Claude.app] [--pid PID] [--channel stable|staging].'
|
|
69
|
+
print -r -- 'The installer and recovery lifecycle use only built-in macOS system tools.'
|
|
70
|
+
exit 0 ;;
|
|
71
|
+
--app|--pid|--storage-root|--user-data-dir|--channel)
|
|
72
|
+
(( $# >= 2 )) || sc_fail "Missing value for $1"
|
|
73
|
+
case "$1" in
|
|
74
|
+
--app) sc_app=$2 ;;
|
|
75
|
+
--pid) sc_pid=$2 ;;
|
|
76
|
+
--storage-root) sc_root=$2 ;;
|
|
77
|
+
--user-data-dir) sc_user_data=$2 ;;
|
|
78
|
+
--channel) sc_channel=$2 ;;
|
|
79
|
+
esac
|
|
80
|
+
shift 2 ;;
|
|
81
|
+
*) sc_fail "Unknown Claude installer argument: $1" ;;
|
|
82
|
+
esac
|
|
83
|
+
done
|
|
84
|
+
[[ "$sc_channel" == stable || "$sc_channel" == staging ]] || sc_fail 'Claude extension channel must be stable or staging.'
|
|
85
|
+
[[ "$OSTYPE" == darwin* && "$EUID" != 0 ]] || sc_fail 'Run this installer as your normal macOS user, not sudo.'
|
|
86
|
+
sc_assert_path "$sc_root"
|
|
87
|
+
sc_root=${sc_root:A}
|
|
88
|
+
sc_select_app
|
|
89
|
+
/bin/mkdir -p "$sc_root"
|
|
90
|
+
sc_acquire_lock
|
|
91
|
+
trap sc_cleanup EXIT
|
|
92
|
+
trap 'exit 130' INT
|
|
93
|
+
trap 'exit 143' TERM HUP
|
|
94
|
+
sc_identity=$(print -rn -- "$sc_app" | /usr/bin/openssl dgst -sha256 | /usr/bin/awk '{print $NF}')
|
|
95
|
+
sc_installation="$sc_root/installations/$sc_identity"
|
|
96
|
+
if [[ -z "$sc_user_data" ]]; then
|
|
97
|
+
sc_user_data="$HOME/Library/Application Support/Claude"
|
|
98
|
+
fi
|
|
99
|
+
sc_assert_path "$sc_user_data"
|
|
100
|
+
sc_user_data=${sc_user_data:A}
|
|
101
|
+
sc_stage=$(/usr/bin/mktemp -d "$sc_root/install-stage.XXXXXXXX")
|
|
102
|
+
sc_version=$(<"$sc_distribution/version")
|
|
103
|
+
sc_installation_state "$sc_app" "$sc_installation" "$sc_pid"
|
|
104
|
+
sc_state=$REPLY
|
|
105
|
+
if [[ "$sc_state" == admission-active || "$sc_state" == already-patched ]]; then
|
|
106
|
+
sc_publish_external_runtime
|
|
107
|
+
print -r -- "$sc_state"
|
|
108
|
+
exit 0
|
|
109
|
+
fi
|
|
110
|
+
/usr/bin/codesign --verify --deep --strict "$sc_app"
|
|
111
|
+
sc_asar_plan "$sc_app" "$sc_stage" "$sc_version" "$sc_bootstrap_namespace"
|
|
112
|
+
|
|
113
|
+
# Publish every prerequisite before modifying either sealed resource.
|
|
114
|
+
sc_publish_external_runtime
|
|
115
|
+
sc_pristine="$sc_installation/pristine/Claude.app/Contents"
|
|
116
|
+
/bin/mkdir -p "$sc_pristine/Resources"
|
|
117
|
+
sc_copy_atomic "$sc_app/Contents/Resources/app.asar" "$sc_pristine/Resources/app.asar"
|
|
118
|
+
sc_copy_atomic "$sc_app/Contents/Info.plist" "$sc_pristine/Info.plist"
|
|
119
|
+
sc_process
|
|
120
|
+
sc_asar_apply "$sc_app" "$sc_stage" "$sc_installation/installed.json"
|
|
121
|
+
print -r -- 'patched'
|
|
122
|
+
print -r -- 'Supercode installed. Quit Claude completely and reopen it.'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/bin/zsh -f
|
|
2
|
+
emulate -LR zsh
|
|
3
|
+
setopt ERR_EXIT NO_UNSET PIPE_FAIL EXTENDED_GLOB
|
|
4
|
+
sc_distribution=${0:A:h:h}
|
|
5
|
+
source "$sc_distribution/macos/common.zsh"
|
|
6
|
+
source "$sc_distribution/macos/claude-process.zsh"
|
|
7
|
+
|
|
8
|
+
zmodload zsh/datetime
|
|
9
|
+
|
|
10
|
+
sc_recovery_log() {
|
|
11
|
+
printf '%.6f %s\n' "$EPOCHREALTIME" "$*"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
sc_wait_for_source_exit() {
|
|
15
|
+
local tick
|
|
16
|
+
for tick in {1..100}; do
|
|
17
|
+
kill -0 "$sc_pid" 2>/dev/null || return 0
|
|
18
|
+
/bin/sleep 0.1
|
|
19
|
+
done
|
|
20
|
+
! kill -0 "$sc_pid" 2>/dev/null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
sc_app='' sc_pid='' sc_root='' sc_user_data='' sc_bootstrap_namespace=''
|
|
24
|
+
while (( $# )); do
|
|
25
|
+
(( $# >= 2 )) || exit 0
|
|
26
|
+
case "$1" in
|
|
27
|
+
--app) sc_app=$2 ;;
|
|
28
|
+
--pid) sc_pid=$2 ;;
|
|
29
|
+
--storage-root) sc_root=$2 ;;
|
|
30
|
+
--bootstrap-namespace) sc_bootstrap_namespace=$2 ;;
|
|
31
|
+
--user-data-dir) sc_user_data=$2 ;;
|
|
32
|
+
*) exit 0 ;;
|
|
33
|
+
esac
|
|
34
|
+
shift 2
|
|
35
|
+
done
|
|
36
|
+
[[ -n "$sc_app" && -n "$sc_pid" && -n "$sc_root" && -n "$sc_user_data" ]] || exit 0
|
|
37
|
+
[[ "$sc_bootstrap_namespace" == .s || "$sc_bootstrap_namespace" == .d || "$sc_bootstrap_namespace" == .e ]] || exit 0
|
|
38
|
+
sc_assert_path "$sc_app"
|
|
39
|
+
sc_assert_path "$sc_root"
|
|
40
|
+
sc_assert_path "$sc_user_data"
|
|
41
|
+
sc_app=${sc_app:A}
|
|
42
|
+
sc_root=${sc_root:A}
|
|
43
|
+
sc_user_data=${sc_user_data:A}
|
|
44
|
+
/bin/mkdir -p "$sc_root/logs"
|
|
45
|
+
exec </dev/null >>"$sc_root/logs/recovery.log" 2>&1
|
|
46
|
+
sc_verify_process "$sc_app" "$sc_pid" || { print -r -- 'relaunch-rejected unexpected-main'; exit 0; }
|
|
47
|
+
sc_identity=$(print -rn -- "$sc_app" | /usr/bin/openssl dgst -sha256 | /usr/bin/awk '{print $NF}')
|
|
48
|
+
sc_installation="$sc_root/installations/$sc_identity"
|
|
49
|
+
[[ -f "$sc_root/recovery-enabled-v1" && -f "$sc_installation/installed.json" ]] || exit 0
|
|
50
|
+
sc_recovery_log "relaunch-start sourcePid=$sc_pid"
|
|
51
|
+
kill -TERM "$sc_pid"
|
|
52
|
+
sc_recovery_log "relaunch-signal-sent sourcePid=$sc_pid"
|
|
53
|
+
|
|
54
|
+
# Claude can receive the first signal while Electron is still installing its
|
|
55
|
+
# lifecycle handlers. Retry the same graceful signal once; never escalate to
|
|
56
|
+
# SIGKILL while the application may be persisting native state.
|
|
57
|
+
if ! sc_wait_for_source_exit; then
|
|
58
|
+
kill -TERM "$sc_pid"
|
|
59
|
+
sc_recovery_log "relaunch-signal-retried sourcePid=$sc_pid"
|
|
60
|
+
fi
|
|
61
|
+
if ! sc_wait_for_source_exit; then
|
|
62
|
+
sc_recovery_log 'relaunch-failed reason=source-still-running'
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
65
|
+
sc_recovery_log "relaunch-source-exited sourcePid=$sc_pid"
|
|
66
|
+
export SUPERCODE_CLAUDE_STORAGE_ROOT="$sc_root"
|
|
67
|
+
export SUPERCODE_CLAUDE_BOOTSTRAP_NAMESPACE="$sc_bootstrap_namespace"
|
|
68
|
+
"$sc_app/Contents/MacOS/Claude" "--user-data-dir=$sc_user_data" </dev/null >/dev/null 2>&1 &
|
|
69
|
+
sc_relaunched_pid=$!
|
|
70
|
+
disown "$sc_relaunched_pid" 2>/dev/null || true
|
|
71
|
+
sc_recovery_log "relaunch-launched sourcePid=$sc_pid pid=$sc_relaunched_pid"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": "0.4",
|
|
3
|
+
"name": "supercode-recovery",
|
|
4
|
+
"display_name": "Supercode Recovery",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "Restores Supercode after Claude Desktop updates.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Supercode"
|
|
9
|
+
},
|
|
10
|
+
"server": {
|
|
11
|
+
"type": "node",
|
|
12
|
+
"entry_point": "server.cjs",
|
|
13
|
+
"mcp_config": {
|
|
14
|
+
"command": "node",
|
|
15
|
+
"args": [
|
|
16
|
+
"${__dirname}/server.cjs"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"tools": []
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";var Ce=Object.create;var Z=Object.defineProperty;var Ie=Object.getOwnPropertyDescriptor;var Ee=Object.getOwnPropertyNames;var xe=Object.getPrototypeOf,ke=Object.prototype.hasOwnProperty;var Oe=(a,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Ee(e))!ke.call(a,n)&&n!==t&&Z(a,n,{get:()=>e[n],enumerable:!(r=Ie(e,n))||r.enumerable});return a};var h=(a,e,t)=>(t=a!=null?Ce(xe(a)):{},Oe(e||!a||!a.__esModule?Z(t,"default",{value:a,enumerable:!0}):t,a));var Re=require("node:module"),B=h(require("node:fs"),1),G=h(require("node:path"),1);var Q=h(require("node:crypto"),1),R=h(require("node:fs"),1),ee=h(require("node:path"),1),F=Symbol.for("supercode.claude.extension-lifecycle-lock.v1"),M=class{constructor(e){this.lockPath=e}lockPath;async run(e){let t=globalThis;if(t[F])throw new Error("Another Supercode Claude lifecycle operation is already in progress");let r={};t[F]=r;let n=null;try{return n=this.acquireFileLock(),await e()}finally{this.releaseFileLock(n),t[F]===r&&delete t[F]}}acquireFileLock(){if(!this.lockPath)return null;R.default.mkdirSync(ee.default.dirname(this.lockPath),{recursive:!0});let e=`${process.pid}:${Q.default.randomUUID()}`;for(let t=0;t<2;t+=1)try{return R.default.symlinkSync(e,this.lockPath),e}catch(r){if(r.code!=="EEXIST"||this.hasLiveOwner())throw new Error("Another Supercode Claude lifecycle operation is already in progress");R.default.rmSync(this.lockPath,{force:!0,recursive:!0})}throw new Error("Another Supercode Claude lifecycle operation is already in progress")}hasLiveOwner(){if(!this.lockPath)return!1;try{let e=Number(R.default.readlinkSync(this.lockPath).split(":",1)[0]);return!Number.isSafeInteger(e)||e<=0?!1:(process.kill(e,0),!0)}catch(e){return e.code==="EPERM"}}releaseFileLock(e){if(!(!this.lockPath||!e))try{R.default.readlinkSync(this.lockPath)===e&&R.default.unlinkSync(this.lockPath)}catch{}}};var Y=require("node:crypto"),re=h(require("node:os"),1),o=h(require("node:path"),1),E="SUPERCODE_CLAUDE_STORAGE_ROOT",te="SUPERCODE_CLAUDE_BOOTSTRAP_NAMESPACE",v=class{root;bootstrapDirectory;constructor(e=process.env,t=re.default.homedir()){this.root=o.default.resolve(e[E]??o.default.join(t,".supercode","claude"));let r=e[te]??".s";if(r!==".s"&&r!==".d"&&r!==".e")throw new Error(`${te} is invalid`);this.bootstrapDirectory=o.default.join(t,r)}get enabledMarker(){return o.default.join(this.root,"recovery-enabled-v1")}get recoveryHook(){return o.default.join(this.root,"recover-v1")}get recoveryLog(){return o.default.join(this.root,"logs","recovery.log")}get recoveryAttemptStateRoot(){return o.default.join(this.root,"recovery-attempt-v1")}get lifecycleLock(){return o.default.join(this.root,"lifecycle.lock")}get extensionChannel(){return o.default.join(this.extensionRoot,"channel-v1")}get extensionDownloadsRoot(){return o.default.join(this.extensionRoot,"downloads")}get extensionRoot(){return o.default.join(this.root,"extension")}get installationsRoot(){return o.default.join(this.root,"installations")}get logsRoot(){return o.default.join(this.root,"logs")}get managedBinariesRoot(){return o.default.join(this.root,"managed-binaries")}get productRoot(){return o.default.join(this.root,"product")}get runtimeRoot(){return o.default.join(this.root,"runtime")}get extensionActiveBootstrap(){return o.default.join(this.extensionRoot,"active.cjs")}get extensionUpdateRetryState(){return o.default.join(this.extensionRoot,"update-retry-v1.json")}get extensionUpdateNotificationState(){return o.default.join(this.extensionRoot,"update-notification-v1.json")}get extensionUnpackingRoot(){return o.default.join(this.extensionRoot,"unpacking")}get extensionReleasesRoot(){return o.default.join(this.extensionRoot,"releases")}get runtimeMain(){return o.default.join(this.root,"runtime","main.cjs")}get runtimeMcpStdio(){return o.default.join(this.root,"runtime","mcp-stdio.zsh")}get runtimePreload(){return o.default.join(this.root,"runtime","preload.cjs")}get runtimeRenderer(){return o.default.join(this.root,"runtime","renderer.js")}get runtimeLog(){return o.default.join(this.root,"logs","runtime.jsonl")}installationRoot(e){let t=(0,Y.createHash)("sha256").update(o.default.resolve(e)).digest("hex");return o.default.join(this.installationsRoot,t)}installationManifest(e){return o.default.join(this.installationRoot(e),"installed.json")}admissionMarker(e){return o.default.join(this.installationRoot(e),"admission-in-progress.json")}pristineApp(e){return o.default.join(this.installationRoot(e),"pristine","Claude.app")}recoveryAttemptState(e){let t=(0,Y.createHash)("sha256").update(e).digest("hex");return o.default.join(this.recoveryAttemptStateRoot,t)}extensionArchive(e){return this.assertReleaseVersion(e),o.default.join(this.extensionDownloadsRoot,`${e}.zip`)}extensionArchivePart(e){return`${this.extensionArchive(e)}.part`}extensionRelease(e){return this.assertReleaseVersion(e),o.default.join(this.extensionReleasesRoot,e)}extensionUnpacking(e){return this.assertReleaseVersion(e),o.default.join(this.extensionUnpackingRoot,e)}assertReleaseVersion(e){if(!/^[A-Za-z0-9][A-Za-z0-9._+-]*$/.test(e))throw new Error("Supercode Claude extension version is invalid")}};var J=h(require("node:crypto"),1),w=h(require("node:path"),1);var ne=h(require("node:crypto"),1),se=h(require("node:fs"),1);var H=class{constructor(e,t,r){this.runtime=e;this.archivePath=t;this.infoPlistPath=r}runtime;archivePath;infoPlistPath;patch(e,t){let r=this.runtime.fs.statSync(this.archivePath).size,n=this.runtime.fs.openSync(this.archivePath,this.openFlags());try{let s=this.read(n,16,0);if(s.readUInt32LE(0)!==4)throw new Error("Unsupported ASAR header");let i=s.readUInt32LE(4),c=s.readUInt32LE(12),l=this.read(n,c,16),y=JSON.parse(l.toString("utf8")),m=8+i,p=this.requirePackedFile(y,e),d=m+this.requireOffset(p,e),u=this.read(n,p.size,d),A=this.requireIntegrity(p,u.length,e),g=t.patch(e,u);if(g.content.length!==u.length)throw new Error("Claude main-entry patch changed the entry size");let f=this.planHeaderPatch(l,p,A,g.content),C=this.digest(l),I=this.digest(f.patchedHeader),S=this.planInfoPlistPatch(C,I);if(g.didPatch)try{this.write(n,g.content,d),this.write(n,f.patchedIntegrity,f.integrityOffset),this.runtime.fs.fsyncSync(n),S&&this.writeFileSegmentInPlace(this.infoPlistPath,S.offset,S.patchedHash),this.verify(d,g.content,f,S)}catch(W){throw this.write(n,u,d),this.write(n,f.integrity,f.integrityOffset),this.runtime.fs.fsyncSync(n),S&&this.writeFileSegmentInPlace(this.infoPlistPath,S.offset,S.hash),W}return{archiveBytes:r,didPatch:g.didPatch,entry:this.segment(d,u,g.content,void 0,f.patchedEntrySha256),entryPath:e,header:this.segment(16,f.header,f.patchedHeader,C,I),headerIntegrity:this.segment(f.integrityOffset,f.integrity,f.patchedIntegrity),...S?{infoPlistAsarIntegrity:this.segment(S.offset,S.hash,S.patchedHash)}:{}}}finally{this.runtime.fs.closeSync(n)}}planHeaderPatch(e,t,r,n){let s=Buffer.from(JSON.stringify(t)),i=this.uniqueOffset(e,s,"ASAR entry metadata"),c=Buffer.from(JSON.stringify(r)),l=this.uniqueOffset(s,c,"ASAR entry integrity metadata"),y=this.digest(n),m={...r,blocks:n.length>0&&n.length<=r.blockSize?[y]:this.blockHashes(n,r.blockSize),hash:y},p=Buffer.from(JSON.stringify(m));if(p.length!==c.length)throw new Error("Patched ASAR entry integrity metadata changed size");let d=Buffer.from(e),u=i+l;return p.copy(d,u),{header:e,integrity:c,integrityOffset:16+u,patchedHeader:d,patchedIntegrity:p,patchedEntrySha256:y}}planInfoPlistPatch(e,t){if(!this.infoPlistPath)return null;let r=this.runtime.fs.readFileSync(this.infoPlistPath),n=r.toString("utf8"),s=n.indexOf("<key>ElectronAsarIntegrity</key>"),i=/<key>hash<\/key>\s*<string>([a-f0-9]{64})<\/string>/.exec(n.slice(s));if(s<0||!i?.[1])throw new Error("ElectronAsarIntegrity hash is unavailable");if(i[1]!==e)throw new Error("ElectronAsarIntegrity mismatch before patch");let c=Buffer.from(e),l=this.uniqueOffset(r,c,"ElectronAsarIntegrity hash");return{hash:c,offset:l,patchedHash:Buffer.from(t)}}verify(e,t,r,n){let s=this.runtime.fs.openSync(this.archivePath,"r");try{if(!this.read(s,r.patchedHeader.length,16).equals(r.patchedHeader))throw new Error("Installed ASAR header does not match the planned bootstrap");if(!this.read(s,t.length,e).equals(t))throw new Error("Installed Claude main entry does not match the planned bootstrap")}finally{this.runtime.fs.closeSync(s)}if(n&&!this.readFileSegment(this.infoPlistPath,n.offset,64).equals(n.patchedHash))throw new Error("Installed ElectronAsarIntegrity hash does not match the ASAR header")}requirePackedFile(e,t){let r=e;for(let n of t.replace(/^\.\//,"").split("/"))r=r?.files?.[n];if(r?.size===void 0||r.offset===void 0||r.unpacked)throw new Error(`ASAR file is unavailable: ${t}`);return r}requireOffset(e,t){let r=Number(e.offset);if(!Number.isSafeInteger(r)||r<0||!Number.isSafeInteger(e.size)||e.size<0)throw new Error(`ASAR file metadata is invalid: ${t}`);return r}requireIntegrity(e,t,r){let n=e.integrity;if(!n||n.algorithm!=="SHA256"||!Number.isSafeInteger(n.blockSize)||n.blockSize<=0||!this.isSha256(n.hash)||!Array.isArray(n.blocks)||n.blocks.length!==Math.ceil(t/n.blockSize)||n.blocks.some(s=>!this.isSha256(s)))throw new Error(`ASAR file has invalid integrity metadata: ${r}`);return n}blockHashes(e,t){let r=[];for(let n=0;n<e.length;n+=t)r.push(this.digest(e.subarray(n,Math.min(n+t,e.length))));return r}segment(e,t,r,n=this.digest(t),s=this.digest(r)){return{bytes:t.length,offset:e,originalSha256:n,patchedSha256:s}}uniqueOffset(e,t,r){let n=e.indexOf(t);if(n<0||e.indexOf(t,n+1)>=0)throw new Error(`${r} must occur exactly once`);return n}isSha256(e){return typeof e=="string"&&/^[a-f0-9]{64}$/.test(e)}digest(e){return this.runtime.crypto.createHash("sha256").update(e).digest("hex")}openFlags(){return this.runtime.fs.constants.O_RDWR|this.runtime.fs.constants.O_NOFOLLOW}read(e,t,r){let n=Buffer.alloc(t),s=0;for(;s<t;){let i=this.runtime.fs.readSync(e,n,s,t-s,r+s);if(i<=0)throw new Error("Unexpected file end");s+=i}return n}readFileSegment(e,t,r){let n=this.runtime.fs.openSync(e,"r");try{return this.read(n,r,t)}finally{this.runtime.fs.closeSync(n)}}write(e,t,r){let n=0;for(;n<t.length;){let s=this.runtime.fs.writeSync(e,t,n,t.length-n,r+n);if(s<=0)throw new Error("In-place write stopped before completion");n+=s}}writeFileSegmentInPlace(e,t,r){let n=this.runtime.fs.openSync(e,this.openFlags());try{this.write(n,r,t),this.runtime.fs.fsyncSync(n)}finally{this.runtime.fs.closeSync(n)}}};var Ne='"use strict";',Be=/(?:\r?\n)?\/\/# sourceMappingURL=[^\r\n]+\s*$/,Fe=/\blet ([\w$]+)=require\("node:os"\);/g,j=class{constructor(e=".s"){this.bootstrapNamespace=e}bootstrapNamespace;reportValue=null;patch(e,t){this.reportValue=null;let r=t.toString("utf8");if(!Buffer.from(r).equals(t))throw new Error("Claude main entry is not canonical UTF-8");if(!r.startsWith(Ne))throw new Error("Claude main entry strict-mode prefix was not found");let n=[...r.matchAll(Fe)];if(n.length!==1)throw new Error("Claude native os-module declaration must occur exactly once");let s=n[0],i=s.index+s[0].length,c=`try{require(${s[1]}.homedir()+"/${this.bootstrapNamespace}")}catch{}`,l=Buffer.byteLength(c);if(r.startsWith(c,i))return this.reportValue={bootstrapBytes:l,windowBytes:l+Buffer.byteLength(/\s*$/.exec(r)[0])},{content:t,didPatch:!1};let y=Be.exec(r);if(!y||i>y.index)throw new Error("Claude bootstrap source-map boundary is unavailable");let m=Buffer.byteLength(y[0]);if(l+1>m)throw new Error("Claude bootstrap does not fit the source-map suffix");let p=r.slice(0,y.index),d=p.slice(0,i)+c+p.slice(i),u=Buffer.alloc(t.length,32);return u.write(d,0,"utf8"),u[u.length-1]=10,this.reportValue={bootstrapBytes:l,windowBytes:m},{content:u,didPatch:!0}}report(){if(!this.reportValue)throw new Error("Claude bootstrap patch has not been planned");return this.reportValue}};var V=class{constructor(e={crypto:ne.default,fs:se.default}){this.runtime=e}runtime;install(e){let t=new H(this.runtime,e.asarPath,e.infoPlistPath),r=new j(e.bootstrapNamespace),n=t.patch(e.entryPath,r),s=r.report();return{...n,bootstrapBytes:s.bootstrapBytes,patchWindowBytes:s.windowBytes}}};var oe=h(require("node:fs"),1),$=h(require("node:path"),1);var ie=require("node:crypto"),ae=h(require("node:fs"),1),x=h(require("node:path"),1),b=class{constructor(e=ae.default){this.filesystem=e}filesystem;write(e,t,r){this.filesystem.mkdirSync(x.default.dirname(e),{recursive:!0,mode:448});let n=x.default.join(x.default.dirname(e),`.${x.default.basename(e)}.${(0,ie.randomUUID)()}.tmp`),s=null;try{s=this.filesystem.openSync(n,"wx",r),this.filesystem.writeFileSync(s,t),this.filesystem.fsyncSync(s),this.filesystem.closeSync(s),s=null,this.filesystem.renameSync(n,e),this.filesystem.chmodSync(e,r),this.syncDirectory(x.default.dirname(e))}catch(i){throw s!==null&&this.filesystem.closeSync(s),this.filesystem.rmSync(n,{force:!0}),i}}writeIfChanged(e,t,r){let n=Buffer.isBuffer(t)?t:Buffer.from(t);return this.filesystem.existsSync(e)&&this.filesystem.readFileSync(e).equals(n)?!1:(this.write(e,n,r),!0)}syncDirectory(e){let t=this.filesystem.openSync(e,"r");try{this.filesystem.fsyncSync(t)}finally{this.filesystem.closeSync(t)}}};var _=class{constructor(e,t=oe.default){this.paths=e;this.filesystem=t;this.writer=new b(t)}paths;filesystem;writer;begin(e,t){let r={schemaVersion:1,appPath:$.default.resolve(e),createdAt:new Date().toISOString(),pid:t.pid,processStartedAt:t.startedAt};this.writer.write(this.paths.admissionMarker(e),`${JSON.stringify(r,null,2)}
|
|
2
|
+
`,384)}isActive(e,t){let r=this.paths.admissionMarker(e);if(!this.filesystem.existsSync(r))return!1;let n;try{n=JSON.parse(this.filesystem.readFileSync(r,"utf8"))}catch{return!1}if(!this.isMarker(n,$.default.resolve(e))||n.pid!==t.pid||n.processStartedAt!==t.startedAt)return!1;try{return process.kill(n.pid,0),!0}catch{return!1}}clear(e,t=process.pid){let r=this.paths.admissionMarker(e);if(!this.filesystem.existsSync(r))return;let n;try{n=JSON.parse(this.filesystem.readFileSync(r,"utf8"))}catch{return}this.isMarker(n,$.default.resolve(e))&&n.pid===t&&this.filesystem.rmSync(r)}isMarker(e,t){if(!e||typeof e!="object")return!1;let r=e;return r.schemaVersion===1&&r.appPath===t&&typeof r.createdAt=="string"&&!Number.isNaN(Date.parse(r.createdAt))&&Number.isSafeInteger(r.pid)&&(r.pid??0)>0&&typeof r.processStartedAt=="string"&&!Number.isNaN(Date.parse(r.processStartedAt))}};var le=require("node:crypto"),ce=h(require("node:fs"),1),de=h(require("node:path"),1);var T=class{constructor(e,t=ce.default){this.paths=e;this.filesystem=t;this.writer=new b(t)}paths;filesystem;writer;read(e){let t=this.filesystem.realpathSync.native(e),r=this.paths.installationManifest(t);if(!this.filesystem.existsSync(r))return null;let n;try{n=JSON.parse(this.filesystem.readFileSync(r,"utf8"))}catch(s){throw new Error(`Claude installation marker cannot be read: ${r}`,{cause:s})}return this.assertManifest(n,t),n}publish(e){this.assertManifest(e,de.default.resolve(e.appPath)),this.writer.write(this.paths.installationManifest(e.appPath),`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,384)}assertManifest(e,t){if(!e||typeof e!="object")throw new Error("Claude installation marker must be an object");let r=e;if(r.schemaVersion!==1||r.appPath!==t||r.bundleIdentifier!=="com.anthropic.claudefordesktop"||typeof r.desktopVersion!="string"||typeof r.bundleVersion!="string"||r.entryPath!==".vite/build/index.pre.js"||typeof r.installedAt!="string"||Number.isNaN(Date.parse(r.installedAt))||typeof r.runtimeVersion!="string"||!this.isResource(r.resources?.asar)||!this.isResource(r.resources.infoPlist))throw new Error("Claude installation marker is invalid");if(r.sealedResourcePatch!==void 0&&!this.isPatch(r.sealedResourcePatch,r.resources.asar.bytes,r.resources.infoPlist.bytes))throw new Error("Claude installation marker has an invalid sealed-resource patch identity")}isPatch(e,t,r){if(!e||typeof e!="object")return!1;let n=e;return this.isSegment(n.asarEntry,t)&&this.isSegment(n.asarEntryIntegrity,t)&&this.isSegment(n.asarHeader,t)&&this.isSegment(n.infoPlistAsarIntegrity,r)}isSegment(e,t){if(!this.isResource(e))return!1;let r=e;return Number.isSafeInteger(r.offset)&&r.offset>=0&&r.offset+r.bytes<=t}isResource(e){if(!e||typeof e!="object")return!1;let t=e;return Number.isSafeInteger(t.bytes)&&(t.bytes??0)>0&&this.isSha256(t.pristineSha256)&&this.isSha256(t.patchedSha256)&&t.pristineSha256!==t.patchedSha256}isSha256(e){return typeof e=="string"&&/^[a-f0-9]{64}$/.test(e)}static sha256(e){return(0,le.createHash)("sha256").update(e).digest("hex")}};var ue=h(require("node:fs"),1),k=h(require("node:path"),1);var D=class{constructor(e,t=ue.default){this.paths=e;this.filesystem=t;this.writer=new b(t)}paths;filesystem;writer;retain(e,t,r){let n=this.paths.pristineApp(e),s=k.default.join(n,"Contents","Resources","app.asar"),i=k.default.join(n,"Contents","Info.plist");if(this.writer.writeIfChanged(s,t,384),this.writer.writeIfChanged(i,r,384),!this.filesystem.readFileSync(s).equals(t)||!this.filesystem.readFileSync(i).equals(r))throw new Error("Claude pristine sealed resources were not retained exactly")}hasFixedSizeResources(e,t,r){let n=this.paths.pristineApp(e);return this.isRealFile(k.default.join(n,"Contents","Resources","app.asar"),t)&&this.isRealFile(k.default.join(n,"Contents","Info.plist"),r)}isRealFile(e,t){try{let r=this.filesystem.lstatSync(e);return r.isFile()&&!r.isSymbolicLink()&&r.size===t}catch(r){if(r.code==="ENOENT")return!1;throw r}}};var he=require("node:crypto"),pe=h(require("node:fs"),1),L=class{constructor(e=pe.default){this.filesystem=e}filesystem;restore(e){this.assertFile(e.sourceAsarPath,e.resourceBytes.asar,"Pristine Claude ASAR"),this.assertFile(e.sourceInfoPlistPath,e.resourceBytes.infoPlist,"Pristine Claude Info.plist"),this.assertFile(e.targetAsarPath,e.resourceBytes.asar,"Patched Claude ASAR"),this.assertFile(e.targetInfoPlistPath,e.resourceBytes.infoPlist,"Patched Claude Info.plist");let t=[{identity:e.patch.asarEntry,sourcePath:e.sourceAsarPath,targetPath:e.targetAsarPath},{identity:e.patch.asarEntryIntegrity,sourcePath:e.sourceAsarPath,targetPath:e.targetAsarPath},{identity:e.patch.infoPlistAsarIntegrity,sourcePath:e.sourceInfoPlistPath,targetPath:e.targetInfoPlistPath}].map(i=>this.prepareTransition(i)),r=this.readFileSegment(e.sourceAsarPath,e.patch.asarHeader.offset,e.patch.asarHeader.bytes),n=this.readFileSegment(e.targetAsarPath,e.patch.asarHeader.offset,e.patch.asarHeader.bytes);this.assertHash(r,e.patch.asarHeader.pristineSha256,"pristine ASAR header"),this.assertHash(n,e.patch.asarHeader.patchedSha256,"patched ASAR header"),this.restoreFile(e.targetAsarPath,t.filter(i=>i.targetPath===e.targetAsarPath)),this.restoreFile(e.targetInfoPlistPath,t.filter(i=>i.targetPath===e.targetInfoPlistPath));for(let i of t){let c=this.readFileSegment(i.targetPath,i.identity.offset,i.identity.bytes);this.assertHash(c,i.identity.pristineSha256,"restored sealed-resource segment")}let s=this.readFileSegment(e.targetAsarPath,e.patch.asarHeader.offset,e.patch.asarHeader.bytes);return this.assertHash(s,e.patch.asarHeader.pristineSha256,"restored ASAR header"),{entryBytes:e.patch.asarEntry.bytes,entrySha256:e.patch.asarEntry.pristineSha256,headerBytes:e.patch.asarHeader.bytes,headerSha256:e.patch.asarHeader.pristineSha256,infoPlistAsarIntegritySha256:e.patch.infoPlistAsarIntegrity.pristineSha256}}prepareTransition(e){let t=this.readFileSegment(e.sourcePath,e.identity.offset,e.identity.bytes),r=this.readFileSegment(e.targetPath,e.identity.offset,e.identity.bytes);return this.assertHash(t,e.identity.pristineSha256,"pristine sealed-resource segment"),this.assertHash(r,e.identity.patchedSha256,"patched sealed-resource segment"),{...e,pristine:t}}restoreFile(e,t){let r=this.filesystem.openSync(e,this.filesystem.constants.O_RDWR|this.filesystem.constants.O_NOFOLLOW);try{for(let n of t)this.write(r,n.pristine,n.identity.offset);this.filesystem.fsyncSync(r)}finally{this.filesystem.closeSync(r)}}assertFile(e,t,r){let n=this.filesystem.lstatSync(e);if(!n.isFile()||n.isSymbolicLink()||n.size!==t)throw new Error(`${r} must be the expected fixed-size real file`)}assertHash(e,t,r){if(this.sha256(e)!==t)throw new Error(`Claude ${r} does not match its installation manifest`)}readFileSegment(e,t,r){let n=this.filesystem.openSync(e,this.filesystem.constants.O_RDONLY|this.filesystem.constants.O_NOFOLLOW);try{let s=Buffer.alloc(r),i=0;for(;i<r;){let c=this.filesystem.readSync(n,s,i,r-i,t+i);if(c<=0)throw new Error(`Unexpected file end while reading sealed-resource segment from ${e}`);i+=c}return s}finally{this.filesystem.closeSync(n)}}write(e,t,r){let n=0;for(;n<t.length;){let s=this.filesystem.writeSync(e,t,n,t.length-n,r+n);if(s<=0)throw new Error("Claude sealed-resource restore stopped before completion");n+=s}}sha256(e){return(0,he.createHash)("sha256").update(e).digest("hex")}};var ye=h(require("node:fs"),1);var Me=3,He=1440*60*1e3,U=class{constructor(e,t=ye.default){this.paths=e;this.filesystem=t;this.writer=new b(t)}paths;filesystem;writer;register(e){let t=this.bundleIdentity(e),r=`${e.mainPid}:${e.mainStartedAt}`,n=this.paths.recoveryAttemptState(t),s=this.read(n,t);if(s?.processIdentity===r)return{attempts:s.attemptCount,bundleIdentity:t,kind:"duplicate"};let i=Date.now(),c=s!==null&&i-s.attemptedAt<He;if(c&&s.attemptCount>=Me)return{attempts:s.attemptCount,bundleIdentity:t,kind:"circuit-open"};let l=c?s.attemptCount+1:1,y={attemptCount:l,attemptedAt:i,bundleIdentity:t,processIdentity:r,schemaVersion:1};return this.writer.write(n,`${JSON.stringify(y)}
|
|
4
|
+
`,384),{attempt:l,bundleIdentity:t,kind:"attempt"}}clear(e){this.filesystem.rmSync(this.paths.recoveryAttemptState(e.bundleIdentity),{force:!0});try{this.filesystem.rmdirSync(this.paths.recoveryAttemptStateRoot)}catch(t){if(!["ENOENT","ENOTEMPTY"].includes(t.code??""))throw t}}bundleIdentity(e){let t=this.filesystem.statSync(e.appPath,{bigint:!0});if(!t.isDirectory())throw new Error("Claude recovery target must be an application directory");return`${t.dev}:${t.ino}:${e.desktopVersion}:${e.bundleVersion}`}read(e,t){if(!this.filesystem.existsSync(e))return null;try{let r=JSON.parse(this.filesystem.readFileSync(e,"utf8"));if(r.schemaVersion===1&&r.bundleIdentity===t&&typeof r.processIdentity=="string"&&r.processIdentity.length>0&&Number.isSafeInteger(r.attemptCount)&&(r.attemptCount??0)>0&&Number.isSafeInteger(r.attemptedAt)&&(r.attemptedAt??0)>0)return r}catch{}return null}};var O=class extends Error{constructor(t,r,n){super(n instanceof Error?n.message:String(n),{cause:n});this.attempt=t;this.bundleIdentity=r;this.name="ClaudeRecoveryAttemptError"}attempt;bundleIdentity},fe=".vite/build/index.pre.js",me="com.anthropic.claudefordesktop",z=class{constructor(e){this.filesystem=e}filesystem;install(e){let t=performance.now(),r=this.filesystem.realpathSync.native(e.appPath),n=new v({[E]:e.storageRoot});if(!this.isRealFile(n.enabledMarker))return this.report("uninstalled",t);let s=new T(n,this.filesystem),i=s.read(r);if(!i)return this.report("uninstalled",t);if(new _(n,this.filesystem).isActive(r,{pid:e.mainPid,startedAt:e.mainStartedAt}))return this.report("admission-active",t);let l=w.default.join(r,"Contents","Resources","app.asar"),y=w.default.join(r,"Contents","Info.plist"),m=this.readRealFile(y,"Claude Info.plist"),p=this.readBundleMetadata(m),d=new U(n,this.filesystem),u=d.register({appPath:r,bundleVersion:p.bundleVersion,desktopVersion:p.desktopVersion,mainPid:e.mainPid,mainStartedAt:e.mainStartedAt});if(u.kind==="circuit-open")return this.report("circuit-open",t,void 0,u);if(u.kind==="duplicate")return this.report("duplicate-attempt",t,void 0,u);let A=null,g=null,f;try{g=this.realFileBytes(l,"Claude ASAR");let C=i.bundleVersion===p.bundleVersion&&i.desktopVersion===p.desktopVersion&&i.resources.asar.bytes===g&&i.resources.infoPlist.bytes===m.length,I=new D(n,this.filesystem);(!C||!I.hasFixedSizeResources(r,g,m.length))&&I.retain(r,this.readRealFile(l,"Claude ASAR"),m);let S=new V({crypto:J.default,fs:this.filesystem}),W=performance.now();if(A=S.install({asarPath:l,bootstrapNamespace:e.bootstrapNamespace,entryPath:fe,infoPlistPath:y}),f=performance.now()-W,!A.didPatch)return d.clear(u),this.report("already-patched",t,f,u);if(!A.infoPlistAsarIntegrity)throw new Error("Claude bootstrap patch did not expose its Info.plist integrity segment");let X=this.patchIdentity(A),ve=this.reuseResourceIdentities(i,p,X)?i.resources:this.resourceIdentities(n,r,l,y,g,m.length),we={schemaVersion:1,appPath:r,bundleIdentifier:me,bundleVersion:p.bundleVersion,desktopVersion:p.desktopVersion,entryPath:fe,installedAt:new Date().toISOString(),resources:ve,runtimeVersion:e.runtimeVersion,sealedResourcePatch:X};return s.publish(we),d.clear(u),this.report("patched",t,f,u)}catch(C){throw A?.didPatch&&A.infoPlistAsarIntegrity&&g!==null&&new L(this.filesystem).restore({patch:this.patchIdentity(A),resourceBytes:{asar:g,infoPlist:m.length},sourceAsarPath:w.default.join(n.pristineApp(r),"Contents","Resources","app.asar"),sourceInfoPlistPath:w.default.join(n.pristineApp(r),"Contents","Info.plist"),targetAsarPath:l,targetInfoPlistPath:y}),new O(u.attempt,u.bundleIdentity,C)}}report(e,t,r,n){return{...n===void 0?{}:{attempts:n.kind==="attempt"?n.attempt:n.attempts,bundleIdentity:n.bundleIdentity},...r===void 0?{}:{patchMs:r},result:e,totalMs:performance.now()-t}}patchIdentity(e){return{asarEntry:this.segmentIdentity(e.entry),asarEntryIntegrity:this.segmentIdentity(e.headerIntegrity),asarHeader:this.segmentIdentity(e.header),infoPlistAsarIntegrity:this.segmentIdentity(e.infoPlistAsarIntegrity)}}segmentIdentity(e){return{bytes:e.bytes,offset:e.offset,patchedSha256:e.patchedSha256,pristineSha256:e.originalSha256}}reuseResourceIdentities(e,t,r){return e.bundleVersion===t.bundleVersion&&e.desktopVersion===t.desktopVersion&&e.sealedResourcePatch!==void 0&&JSON.stringify(e.sealedResourcePatch)===JSON.stringify(r)}resourceIdentities(e,t,r,n,s,i){let c=e.pristineApp(t);return{asar:this.resourceIdentity(s,w.default.join(c,"Contents","Resources","app.asar"),r),infoPlist:this.resourceIdentity(i,w.default.join(c,"Contents","Info.plist"),n)}}resourceIdentity(e,t,r){return{bytes:e,patchedSha256:this.sha256File(r),pristineSha256:this.sha256File(t)}}readBundleMetadata(e){let t=e.toString("utf8");if(this.readPlistString(t,"CFBundleIdentifier")!==me)throw new Error("Recovery target is not Claude Desktop");return{bundleVersion:this.readPlistString(t,"CFBundleVersion"),desktopVersion:this.readPlistString(t,"CFBundleShortVersionString")}}readPlistString(e,t){let r=[...e.matchAll(new RegExp(`<key>${t}</key>\\s*<string>([^<]+)</string>`,"g"))];if(r.length!==1||!r[0]?.[1])throw new Error(`Claude Info.plist ${t} must occur exactly once`);return r[0][1]}realFileBytes(e,t){let r=this.filesystem.lstatSync(e);if(!r.isFile()||r.isSymbolicLink())throw new Error(`${t} must be a real file`);return r.size}readRealFile(e,t){return this.realFileBytes(e,t),this.filesystem.readFileSync(e)}isRealFile(e){try{let t=this.filesystem.lstatSync(e);return t.isFile()&&!t.isSymbolicLink()}catch(t){if(t.code==="ENOENT")return!1;throw t}}sha256File(e){let t=this.filesystem.openSync(e,"r"),r=(0,J.createHash)("sha256"),n=Buffer.allocUnsafe(1024*1024);try{let s=0;do s=this.filesystem.readSync(t,n,0,n.length,null),r.update(n.subarray(0,s));while(s>0)}finally{this.filesystem.closeSync(t)}return r.digest("hex")}};var Se=h(require("node:path"),1);function Pe(a){if(!a||typeof a!="object")throw new Error("Claude recovery configuration must be an object");let e=a;if(e.schemaVersion!==1||!ge(e.coordinatorHook)||!ge(e.storageRoot)||typeof e.runtimeVersion!="string"||e.runtimeVersion.length===0||!je(e.bootstrapNamespace))throw new Error("Claude recovery configuration is invalid");return e}function ge(a){return typeof a=="string"&&Se.default.isAbsolute(a)&&![...a].some(e=>{let t=e.codePointAt(0);return t<=31||t===127})}function je(a){return a===".s"||a===".d"||a===".e"}var N=require("node:child_process"),P=h(require("node:path"),1);var be=require("node:child_process");function Ae(a){if(!Number.isSafeInteger(a)||a<=1)throw new Error("Claude process ID is invalid");let e=(0,be.execFileSync)("/bin/ps",["-p",String(a),"-o","lstart="],{encoding:"utf8",env:{...process.env,LC_ALL:"C"}}).trim().replace(/\s+/g," ");if(!e||Number.isNaN(Date.parse(e)))throw new Error("Claude process start time is unavailable");return{pid:a,startedAt:e}}var Ve=`${P.default.sep}Contents${P.default.sep}Frameworks${P.default.sep}`,q=class{constructor(e,t){this.extensionDirectory=e;this.filesystem=t}extensionDirectory;filesystem;discover(){let e=this.filesystem.realpathSync.native(this.extensionDirectory);if(P.default.basename(e)!=="local.supercode-recovery"||P.default.basename(P.default.dirname(e))!=="Claude Extensions")throw new Error("Recovery server is not installed in the expected Claude extension directory");let t=this.filesystem.realpathSync.native(process.execPath),r=t.indexOf(Ve);if(r<=0||!t.includes(`${P.default.sep}Claude Helper (Plugin).app${P.default.sep}`))throw new Error("Recovery did not start in Claude Helper (Plugin)");let n=this.filesystem.realpathSync.native(t.slice(0,r));if(P.default.basename(n)!=="Claude.app")throw new Error("Recovery helper does not belong to Claude.app");let s=process.ppid;if(!Number.isSafeInteger(s)||s<=1)throw new Error("Claude recovery parent PID is invalid");let i=P.default.join(n,"Contents","MacOS","Claude"),c=(0,N.execFileSync)("/bin/ps",["-p",String(s),"-o","comm="],{encoding:"utf8"}).trim(),l=Number((0,N.execFileSync)("/bin/ps",["-p",String(s),"-o","uid="],{encoding:"utf8"}).trim());if(this.filesystem.realpathSync.native(c)!==this.filesystem.realpathSync.native(i))throw new Error("Recovery helper parent is not the exact Claude main executable");if(l!==process.getuid?.())throw new Error("Recovery helper parent belongs to another user");return{appPath:n,mainPid:s,mainStartedAt:Ae(s).startedAt,userDataPath:P.default.resolve(e,"..","..")}}coordinateRelaunch(e,t){(0,N.spawn)("/bin/zsh",["-f",t.coordinatorHook,"--app",e.appPath,"--pid",String(e.mainPid),"--storage-root",t.storageRoot,"--bootstrap-namespace",t.bootstrapNamespace,"--user-data-dir",e.userDataPath],{detached:!0,env:process.env,stdio:"ignore"}).unref()}};var K=class{input="";start(){process.stdin.setEncoding("utf8"),process.stdin.on("data",e=>this.receive(e)),setImmediate(()=>{this.recover()})}async recover(){let e=performance.now(),t=G.default.join(__dirname,"recovery.json");if(!B.default.existsSync(t))return;let r=null;try{r=Pe(JSON.parse(B.default.readFileSync(t,"utf8")));let n=r;this.write(n,"recovery-started");let i=(0,Re.createRequire)(__filename)("original-fs"),c=new q(__dirname,i),l=c.discover(),y=new v({[E]:r.storageRoot});await new M(y.lifecycleLock).run(()=>{let p=new z(i);this.write(n,`recovery-patch-started pid=${l.mainPid}`);let d=p.install({appPath:l.appPath,bootstrapNamespace:n.bootstrapNamespace,mainPid:l.mainPid,mainStartedAt:l.mainStartedAt,runtimeVersion:n.runtimeVersion,storageRoot:n.storageRoot});if(d.result==="circuit-open"){this.write(n,`circuit-open identity=${d.bundleIdentity} attempts=${d.attempts??0} pid=${l.mainPid}`);return}if(d.result==="duplicate-attempt"){this.write(n,`recovery-skipped reason=duplicate identity=${d.bundleIdentity} attempts=${d.attempts??0} pid=${l.mainPid}`);return}this.write(n,`recovery-result result=${d.result} pid=${l.mainPid} patchMs=${d.patchMs?.toFixed(3)??"0"} installationMs=${d.totalMs.toFixed(3)} totalMs=${this.millisecondsSince(e)}`),d.result==="patched"&&c.coordinateRelaunch(l,n)})}catch(n){let s=n instanceof O?` identity=${n.bundleIdentity} attempt=${n.attempt}`:"";this.write(r,`recovery-failed${s} error=${this.formatError(n)}`)}}millisecondsSince(e){return(performance.now()-e).toFixed(3)}receive(e){for(this.input+=e;this.input.includes(`
|
|
5
|
+
`);){let t=this.input.indexOf(`
|
|
6
|
+
`),r=this.input.slice(0,t);if(this.input=this.input.slice(t+1),!!r)try{this.respond(JSON.parse(r))}catch{}}}respond(e){if(e.id===void 0)return;let t={};e.method==="initialize"?t={capabilities:{},protocolVersion:typeof e.params?.protocolVersion=="string"?e.params.protocolVersion:"2025-06-18",serverInfo:{name:"supercode-recovery",version:"0.1.0"}}:e.method==="tools/list"?t={tools:[]}:e.method==="resources/list"?t={resources:[]}:e.method==="prompts/list"&&(t={prompts:[]}),process.stdout.write(`${JSON.stringify({id:e.id,jsonrpc:"2.0",result:t})}
|
|
7
|
+
`)}write(e,t){try{if(!e)return;let r=new v({[E]:e.storageRoot});B.default.mkdirSync(G.default.dirname(r.recoveryLog),{mode:448,recursive:!0}),B.default.appendFileSync(r.recoveryLog,`${new Date().toISOString()} ${t}
|
|
8
|
+
`,{mode:384})}catch{}}formatError(e){return e instanceof Error?e.message.replace(/[\r\n]+/g," "):String(e).replace(/[\r\n]+/g," ")}},$e=new K;$e.start();
|
package/dist/version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.1
|
package/package.json
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supercode-sh/claude-install-mac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Installer for the Supercode Claude Desktop integration on macOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"claude-install-mac": "
|
|
7
|
+
"claude-install-mac": "dist/claude-install-mac"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"
|
|
10
|
+
"dist/",
|
|
11
11
|
"README.md"
|
|
12
12
|
],
|
|
13
|
+
"os": [
|
|
14
|
+
"darwin"
|
|
15
|
+
],
|
|
13
16
|
"engines": {
|
|
14
17
|
"node": ">=18"
|
|
15
18
|
},
|
|
16
19
|
"scripts": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
+
"prebuild": "npm --prefix ../extension run build:production",
|
|
21
|
+
"build": "node scripts/build.cjs",
|
|
22
|
+
"check:build": "node --check scripts/build.cjs && node --check scripts/ShellDistribution.cjs && /bin/zsh -n bin/claude-install-mac && /bin/zsh -n macos/common.zsh && /bin/zsh -n macos/claude-asar-bootstrap.zsh && /bin/zsh -n macos/claude-installation-state.zsh && /bin/zsh -n macos/claude-process.zsh && /bin/zsh -n macos/claude-recovery-extension.zsh && /bin/zsh -n macos/install.zsh && /bin/zsh -n macos/recover.zsh && /bin/zsh -n macos/mcp-stdio.zsh",
|
|
23
|
+
"verify:dist": "node scripts/build.cjs --verify-only",
|
|
20
24
|
"prepack": "npm --prefix .. run check && npm run verify:dist"
|
|
21
25
|
},
|
|
22
26
|
"publishConfig": {
|
package/bin/noop.mjs
DELETED