claude-cac 1.2.0 → 1.3.0
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 +42 -30
- package/cac +564 -229
- package/fingerprint-hook.js +14 -2
- package/package.json +1 -1
- package/relay.js +17 -9
package/cac
CHANGED
|
@@ -10,7 +10,8 @@ VERSIONS_DIR="$CAC_DIR/versions"
|
|
|
10
10
|
# ━━━ utils.sh ━━━
|
|
11
11
|
# ── utils: 颜色、读写、UUID、proxy 解析 ───────────────────────
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# shellcheck disable=SC2034 # used in build-concatenated cac script
|
|
14
|
+
CAC_VERSION="1.3.0"
|
|
14
15
|
|
|
15
16
|
_read() { [[ -f "$1" ]] && tr -d '[:space:]' < "$1" || echo "${2:-}"; }
|
|
16
17
|
_die() { printf '%b\n' "$(_red "error:") $*" >&2; exit 1; }
|
|
@@ -68,7 +69,7 @@ _parse_proxy() {
|
|
|
68
69
|
host=$(echo "$raw" | cut -d: -f1)
|
|
69
70
|
port=$(echo "$raw" | cut -d: -f2)
|
|
70
71
|
user=$(echo "$raw" | cut -d: -f3)
|
|
71
|
-
pass=$(echo "$raw" | cut -d: -f4)
|
|
72
|
+
pass=$(echo "$raw" | cut -d: -f4-)
|
|
72
73
|
if [[ -z "$user" ]]; then
|
|
73
74
|
echo "http://${host}:${port}"
|
|
74
75
|
else
|
|
@@ -103,7 +104,7 @@ _auto_detect_proxy() {
|
|
|
103
104
|
host=$(echo "$raw" | cut -d: -f1)
|
|
104
105
|
port=$(echo "$raw" | cut -d: -f2)
|
|
105
106
|
user=$(echo "$raw" | cut -d: -f3)
|
|
106
|
-
pass=$(echo "$raw" | cut -d: -f4)
|
|
107
|
+
pass=$(echo "$raw" | cut -d: -f4-)
|
|
107
108
|
if [[ -n "$user" ]]; then
|
|
108
109
|
auth_part="${user}:${pass}@"
|
|
109
110
|
else
|
|
@@ -134,6 +135,25 @@ _env_dir() { echo "$ENVS_DIR/$1"; }
|
|
|
134
135
|
|
|
135
136
|
# ── Version management helpers ────────────────────────────────────
|
|
136
137
|
|
|
138
|
+
# Find the highest installed version by semver sort
|
|
139
|
+
_update_latest() {
|
|
140
|
+
local highest=""
|
|
141
|
+
for d in "$VERSIONS_DIR"/*/; do
|
|
142
|
+
[[ -d "$d" ]] || continue
|
|
143
|
+
local v
|
|
144
|
+
v=$(basename "$d")
|
|
145
|
+
[[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]] || continue
|
|
146
|
+
if [[ -z "$highest" ]] || [[ "$(printf '%s\n%s\n' "$highest" "$v" | sort -t. -k1,1n -k2,2n -k3,3n | tail -1)" == "$v" ]]; then
|
|
147
|
+
highest="$v"
|
|
148
|
+
fi
|
|
149
|
+
done
|
|
150
|
+
if [[ -n "$highest" ]]; then
|
|
151
|
+
echo "$highest" > "$VERSIONS_DIR/.latest"
|
|
152
|
+
else
|
|
153
|
+
rm -f "$VERSIONS_DIR/.latest"
|
|
154
|
+
fi
|
|
155
|
+
}
|
|
156
|
+
|
|
137
157
|
_resolve_version() {
|
|
138
158
|
local v="$1"
|
|
139
159
|
if [[ "$v" == "latest" || -z "$v" ]]; then
|
|
@@ -195,8 +215,8 @@ _ensure_version_installed() {
|
|
|
195
215
|
if [[ ! -x "$(_version_binary "$ver")" ]]; then
|
|
196
216
|
echo "Version $(_cyan "$ver") not installed, downloading ..." >&2
|
|
197
217
|
mkdir -p "$VERSIONS_DIR"
|
|
198
|
-
_download_version "$ver" || return 1
|
|
199
|
-
|
|
218
|
+
_download_version "$ver" >&2 || return 1
|
|
219
|
+
_update_latest
|
|
200
220
|
echo >&2
|
|
201
221
|
fi
|
|
202
222
|
echo "$ver"
|
|
@@ -245,15 +265,22 @@ _find_real_claude() {
|
|
|
245
265
|
}
|
|
246
266
|
|
|
247
267
|
_detect_rc_file() {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
268
|
+
local shell_name
|
|
269
|
+
shell_name=$(basename "${SHELL:-/bin/bash}")
|
|
270
|
+
case "$shell_name" in
|
|
271
|
+
zsh)
|
|
272
|
+
[[ -f "$HOME/.zshrc" ]] && { echo "$HOME/.zshrc"; return; }
|
|
273
|
+
;;
|
|
274
|
+
bash)
|
|
275
|
+
[[ -f "$HOME/.bashrc" ]] && { echo "$HOME/.bashrc"; return; }
|
|
276
|
+
[[ -f "$HOME/.bash_profile" ]] && { echo "$HOME/.bash_profile"; return; }
|
|
277
|
+
;;
|
|
278
|
+
esac
|
|
279
|
+
# Fallback: try common rc files
|
|
280
|
+
[[ -f "$HOME/.bashrc" ]] && { echo "$HOME/.bashrc"; return; }
|
|
281
|
+
[[ -f "$HOME/.zshrc" ]] && { echo "$HOME/.zshrc"; return; }
|
|
282
|
+
[[ -f "$HOME/.bash_profile" ]] && { echo "$HOME/.bash_profile"; return; }
|
|
283
|
+
echo ""
|
|
257
284
|
}
|
|
258
285
|
|
|
259
286
|
_install_method() {
|
|
@@ -292,13 +319,20 @@ _write_path_to_rc() {
|
|
|
292
319
|
_remove_path_from_rc "$rc_file"
|
|
293
320
|
fi
|
|
294
321
|
|
|
295
|
-
cat >> "$rc_file" << '
|
|
322
|
+
cat >> "$rc_file" << 'CACEOF'
|
|
296
323
|
|
|
297
324
|
# >>> cac — Claude Code Cloak >>>
|
|
298
|
-
|
|
299
|
-
export PATH="$HOME/.cac/bin:$PATH"
|
|
325
|
+
PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '\.cac/bin' | grep -v "$HOME/bin" | tr '\n' ':' | sed 's/:$//')
|
|
326
|
+
export PATH="$HOME/.cac/bin:$HOME/bin:$PATH"
|
|
327
|
+
cac() {
|
|
328
|
+
command "$HOME/bin/cac" "$@"
|
|
329
|
+
local _rc=$?
|
|
330
|
+
PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '\.cac/bin' | tr '\n' ':' | sed 's/:$//')
|
|
331
|
+
export PATH="$HOME/.cac/bin:$HOME/bin:$PATH"
|
|
332
|
+
return $_rc
|
|
333
|
+
}
|
|
300
334
|
# <<< cac — Claude Code Cloak <<<
|
|
301
|
-
|
|
335
|
+
CACEOF
|
|
302
336
|
echo " ✓ PATH 已写入 $rc_file"
|
|
303
337
|
return 0
|
|
304
338
|
}
|
|
@@ -333,9 +367,13 @@ _update_statsig() {
|
|
|
333
367
|
local config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
334
368
|
local statsig="$config_dir/statsig"
|
|
335
369
|
[[ -d "$statsig" ]] || return 0
|
|
370
|
+
local found=false
|
|
336
371
|
for f in "$statsig"/statsig.stable_id.*; do
|
|
337
|
-
[[ -f "$f" ]] && printf '"%s"' "$sid" > "$f"
|
|
372
|
+
[[ -f "$f" ]] && { printf '"%s"' "$sid" > "$f"; found=true; }
|
|
338
373
|
done
|
|
374
|
+
if [[ "$found" == "false" ]]; then
|
|
375
|
+
printf '"%s"' "$sid" > "$statsig/statsig.stable_id.local"
|
|
376
|
+
fi
|
|
339
377
|
}
|
|
340
378
|
|
|
341
379
|
_update_claude_json_user_id() {
|
|
@@ -345,11 +383,16 @@ _update_claude_json_user_id() {
|
|
|
345
383
|
[[ -f "$claude_json" ]] || claude_json="$HOME/.claude.json"
|
|
346
384
|
[[ -f "$claude_json" ]] || return 0
|
|
347
385
|
python3 - "$claude_json" "$user_id" << 'PYEOF'
|
|
348
|
-
import json, sys
|
|
386
|
+
import json, sys, uuid
|
|
349
387
|
fpath, uid = sys.argv[1], sys.argv[2]
|
|
350
388
|
with open(fpath) as f:
|
|
351
389
|
d = json.load(f)
|
|
352
390
|
d['userID'] = uid
|
|
391
|
+
d['anonymousId'] = 'claudecode.v1.' + str(uuid.uuid4())
|
|
392
|
+
d.pop('numStartups', None)
|
|
393
|
+
d.pop('firstStartTime', None)
|
|
394
|
+
d.pop('cachedGrowthBookFeatures', None)
|
|
395
|
+
d.pop('cachedStatsigGates', None)
|
|
353
396
|
with open(fpath, 'w') as f:
|
|
354
397
|
json.dump(d, f, indent=2, ensure_ascii=False)
|
|
355
398
|
PYEOF
|
|
@@ -364,6 +407,7 @@ TELEMETRY_DOMAINS=(
|
|
|
364
407
|
"statsig.anthropic.com"
|
|
365
408
|
"sentry.io"
|
|
366
409
|
"o1137031.ingest.sentry.io"
|
|
410
|
+
"cdn.growthbook.io"
|
|
367
411
|
)
|
|
368
412
|
|
|
369
413
|
# 写入 HOSTALIASES 文件(备用层:gethostbyname 级别拦截)
|
|
@@ -403,6 +447,7 @@ var BLOCKED_DOMAINS = new Set([
|
|
|
403
447
|
'statsig.anthropic.com',
|
|
404
448
|
'sentry.io',
|
|
405
449
|
'o1137031.ingest.sentry.io',
|
|
450
|
+
'cdn.growthbook.io',
|
|
406
451
|
]);
|
|
407
452
|
|
|
408
453
|
/**
|
|
@@ -877,7 +922,137 @@ _check_mtls() {
|
|
|
877
922
|
}
|
|
878
923
|
|
|
879
924
|
# ━━━ templates.sh ━━━
|
|
880
|
-
# ── templates: 写入 wrapper
|
|
925
|
+
# ── templates: 写入 wrapper、shim、环境初始化 ──────────────────
|
|
926
|
+
|
|
927
|
+
# 写入 statusline-command.sh 到环境 .claude 目录
|
|
928
|
+
_write_statusline_script() {
|
|
929
|
+
local config_dir="$1"
|
|
930
|
+
cat > "$config_dir/statusline-command.sh" << 'STATUSLINE_EOF'
|
|
931
|
+
#!/usr/bin/env bash
|
|
932
|
+
input=$(cat)
|
|
933
|
+
|
|
934
|
+
model=$(echo "$input" | jq -r '.model.display_name // empty')
|
|
935
|
+
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
936
|
+
version=$(echo "$input" | jq -r '.version // empty')
|
|
937
|
+
session_name=$(echo "$input" | jq -r '.session_name // empty')
|
|
938
|
+
|
|
939
|
+
used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
|
|
940
|
+
ctx_size=$(echo "$input" | jq -r '.context_window.context_window_size // empty')
|
|
941
|
+
input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // empty')
|
|
942
|
+
|
|
943
|
+
five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
|
|
944
|
+
week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
|
|
945
|
+
five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
|
|
946
|
+
|
|
947
|
+
worktree_name=$(echo "$input" | jq -r '.worktree.name // empty')
|
|
948
|
+
worktree_branch=$(echo "$input" | jq -r '.worktree.branch // empty')
|
|
949
|
+
|
|
950
|
+
reset='\033[0m'; bold='\033[1m'; dim='\033[2m'
|
|
951
|
+
cyan='\033[36m'; yellow='\033[33m'; green='\033[32m'; red='\033[31m'
|
|
952
|
+
magenta='\033[35m'; blue='\033[34m'
|
|
953
|
+
|
|
954
|
+
parts=()
|
|
955
|
+
|
|
956
|
+
[ -n "$model" ] && parts+=("$(printf "${cyan}${bold}%s${reset}" "$model")")
|
|
957
|
+
[ -n "$version" ] && parts+=("$(printf "${dim}v%s${reset}" "$version")")
|
|
958
|
+
[ -n "$session_name" ] && parts+=("$(printf "${magenta}[%s]${reset}" "$session_name")")
|
|
959
|
+
|
|
960
|
+
if [ -n "$cwd" ]; then
|
|
961
|
+
short_cwd="${cwd/#$HOME/~}"
|
|
962
|
+
parts+=("$(printf "${blue}%s${reset}" "$short_cwd")")
|
|
963
|
+
fi
|
|
964
|
+
|
|
965
|
+
if [ -n "$used_pct" ] && [ -n "$ctx_size" ]; then
|
|
966
|
+
ctx_int=$(printf "%.0f" "$used_pct")
|
|
967
|
+
if [ "$ctx_int" -ge 80 ]; then ctx_color="$red"
|
|
968
|
+
elif [ "$ctx_int" -ge 50 ]; then ctx_color="$yellow"
|
|
969
|
+
else ctx_color="$green"; fi
|
|
970
|
+
ctx_k=$(echo "$ctx_size" | awk '{printf "%.0fk", $1/1000}')
|
|
971
|
+
if [ -n "$input_tokens" ]; then
|
|
972
|
+
tokens_k=$(echo "$input_tokens" | awk '{printf "%.1fk", $1/1000}')
|
|
973
|
+
parts+=("$(printf "${ctx_color}ctx:%.0f%% %s/%s${reset}" "$used_pct" "${tokens_k}" "${ctx_k}")")
|
|
974
|
+
else
|
|
975
|
+
parts+=("$(printf "${ctx_color}ctx:%.0f%% (%s)${reset}" "$used_pct" "${ctx_k}")")
|
|
976
|
+
fi
|
|
977
|
+
fi
|
|
978
|
+
|
|
979
|
+
rate_parts=()
|
|
980
|
+
if [ -n "$five_pct" ]; then
|
|
981
|
+
five_int=$(printf "%.0f" "$five_pct")
|
|
982
|
+
if [ "$five_int" -ge 80 ]; then rc="$red"
|
|
983
|
+
elif [ "$five_int" -ge 50 ]; then rc="$yellow"
|
|
984
|
+
else rc="$green"; fi
|
|
985
|
+
rs="$(printf "${rc}5h:%.0f%%${reset}" "$five_pct")"
|
|
986
|
+
if [ -n "$five_reset" ] && [ "$five_int" -ge 50 ]; then
|
|
987
|
+
rm=$(( (five_reset - $(date +%s)) / 60 ))
|
|
988
|
+
[ "$rm" -gt 0 ] && rs="$rs$(printf "${dim}(${rm}m)${reset}")"
|
|
989
|
+
fi
|
|
990
|
+
rate_parts+=("$rs")
|
|
991
|
+
fi
|
|
992
|
+
if [ -n "$week_pct" ]; then
|
|
993
|
+
week_int=$(printf "%.0f" "$week_pct")
|
|
994
|
+
if [ "$week_int" -ge 80 ]; then wc="$red"
|
|
995
|
+
elif [ "$week_int" -ge 50 ]; then wc="$yellow"
|
|
996
|
+
else wc="$green"; fi
|
|
997
|
+
rate_parts+=("$(printf "${wc}7d:%.0f%%${reset}" "$week_pct")")
|
|
998
|
+
fi
|
|
999
|
+
if [ "${#rate_parts[@]}" -gt 0 ]; then
|
|
1000
|
+
rstr="${rate_parts[0]}"
|
|
1001
|
+
for i in "${rate_parts[@]:1}"; do rstr="$rstr $i"; done
|
|
1002
|
+
parts+=("$rstr")
|
|
1003
|
+
fi
|
|
1004
|
+
|
|
1005
|
+
if [ -n "$worktree_name" ]; then
|
|
1006
|
+
wt="wt:$worktree_name"
|
|
1007
|
+
[ -n "$worktree_branch" ] && wt="$wt($worktree_branch)"
|
|
1008
|
+
parts+=("$(printf "${cyan}%s${reset}" "$wt")")
|
|
1009
|
+
fi
|
|
1010
|
+
|
|
1011
|
+
if [ "${#parts[@]}" -eq 0 ]; then printf "${dim}claude${reset}\n"; exit 0; fi
|
|
1012
|
+
sep="$(printf " ${dim}|${reset} ")"
|
|
1013
|
+
result="${parts[0]}"
|
|
1014
|
+
for p in "${parts[@]:1}"; do result="$result$sep$p"; done
|
|
1015
|
+
printf "%b\n" "$result"
|
|
1016
|
+
STATUSLINE_EOF
|
|
1017
|
+
chmod +x "$config_dir/statusline-command.sh"
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
# 写入 settings.json 到环境 .claude 目录
|
|
1021
|
+
_write_env_settings() {
|
|
1022
|
+
local config_dir="$1"
|
|
1023
|
+
cat > "$config_dir/settings.json" << 'SETTINGS_EOF'
|
|
1024
|
+
{
|
|
1025
|
+
"permissions": {
|
|
1026
|
+
"defaultMode": "bypassPermissions"
|
|
1027
|
+
},
|
|
1028
|
+
"skipDangerousModePermissionPrompt": true,
|
|
1029
|
+
"statusLine": {
|
|
1030
|
+
"type": "command",
|
|
1031
|
+
"command": "bash $CLAUDE_CONFIG_DIR/statusline-command.sh"
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
SETTINGS_EOF
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
# 写入 CLAUDE.md 到环境 .claude 目录
|
|
1038
|
+
_write_env_claude_md() {
|
|
1039
|
+
local config_dir="$1"
|
|
1040
|
+
local env_name="$2"
|
|
1041
|
+
cat > "$config_dir/CLAUDE.md" << CLAUDEMD_EOF
|
|
1042
|
+
# cac managed environment
|
|
1043
|
+
|
|
1044
|
+
This Claude Code instance is managed by **cac** (Claude Code Cloak).
|
|
1045
|
+
|
|
1046
|
+
- Environment name: \`$env_name\`
|
|
1047
|
+
- Config directory: \`CLAUDE_CONFIG_DIR\` is set to this \`.claude/\` folder
|
|
1048
|
+
- Your settings, credentials, and sessions are isolated per-environment
|
|
1049
|
+
|
|
1050
|
+
Useful commands:
|
|
1051
|
+
- \`cac env ls\` — list all environments and their config paths
|
|
1052
|
+
- \`cac env check\` — verify current environment health
|
|
1053
|
+
- \`cac <name>\` — switch to another environment
|
|
1054
|
+
CLAUDEMD_EOF
|
|
1055
|
+
}
|
|
881
1056
|
|
|
882
1057
|
_write_wrapper() {
|
|
883
1058
|
mkdir -p "$CAC_DIR/bin"
|
|
@@ -906,6 +1081,8 @@ _env_dir="$ENVS_DIR/$_name"
|
|
|
906
1081
|
# Isolated .claude config directory
|
|
907
1082
|
if [[ -d "$_env_dir/.claude" ]]; then
|
|
908
1083
|
export CLAUDE_CONFIG_DIR="$_env_dir/.claude"
|
|
1084
|
+
# 确保 settings.json 存在,阻止 Claude Code fallback 到 ~/.claude/settings.json
|
|
1085
|
+
[[ -f "$_env_dir/.claude/settings.json" ]] || echo '{}' > "$_env_dir/.claude/settings.json"
|
|
909
1086
|
fi
|
|
910
1087
|
|
|
911
1088
|
# Proxy — optional: only if proxy file exists and is non-empty
|
|
@@ -930,9 +1107,13 @@ fi
|
|
|
930
1107
|
if [[ -f "$_env_dir/stable_id" ]]; then
|
|
931
1108
|
_sid=$(tr -d '[:space:]' < "$_env_dir/stable_id")
|
|
932
1109
|
_config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1110
|
+
if [[ -d "$_config_dir/statsig" ]]; then
|
|
1111
|
+
_sid_found=false
|
|
1112
|
+
for _f in "$_config_dir/statsig"/statsig.stable_id.*; do
|
|
1113
|
+
[[ -f "$_f" ]] && { printf '"%s"' "$_sid" > "$_f"; _sid_found=true; }
|
|
1114
|
+
done
|
|
1115
|
+
[[ "$_sid_found" == "false" ]] && printf '"%s"' "$_sid" > "$_config_dir/statsig/statsig.stable_id.local"
|
|
1116
|
+
fi
|
|
936
1117
|
fi
|
|
937
1118
|
|
|
938
1119
|
# 注入环境变量 —— 代理(仅在配置了代理时)
|
|
@@ -1214,6 +1395,13 @@ _ensure_initialized() {
|
|
|
1214
1395
|
_write_dns_guard_js 2>/dev/null || true
|
|
1215
1396
|
_write_blocked_hosts 2>/dev/null || true
|
|
1216
1397
|
|
|
1398
|
+
# PATH (idempotent — always ensure it's in rc file)
|
|
1399
|
+
local rc_file; rc_file=$(_detect_rc_file)
|
|
1400
|
+
_write_path_to_rc "$rc_file" >/dev/null 2>&1 || true
|
|
1401
|
+
|
|
1402
|
+
# Keep .latest pointing to highest installed version
|
|
1403
|
+
_update_latest 2>/dev/null || true
|
|
1404
|
+
|
|
1217
1405
|
# Rest only needed on first init
|
|
1218
1406
|
[[ -f "$CAC_DIR/bin/claude" ]] && return 0
|
|
1219
1407
|
|
|
@@ -1244,10 +1432,6 @@ _ensure_initialized() {
|
|
|
1244
1432
|
|
|
1245
1433
|
# mTLS CA
|
|
1246
1434
|
_generate_ca_cert 2>/dev/null || true
|
|
1247
|
-
|
|
1248
|
-
# PATH (idempotent)
|
|
1249
|
-
local rc_file; rc_file=$(_detect_rc_file)
|
|
1250
|
-
_write_path_to_rc "$rc_file" >/dev/null 2>&1 || true
|
|
1251
1435
|
}
|
|
1252
1436
|
|
|
1253
1437
|
# Explicit setup command — runs initialization with verbose output
|
|
@@ -1305,7 +1489,7 @@ _env_cmd_create() {
|
|
|
1305
1489
|
esac
|
|
1306
1490
|
done
|
|
1307
1491
|
|
|
1308
|
-
[[ -n "$name" ]] || _die "usage: cac env create <name> [-p <proxy>] [-c <version>]
|
|
1492
|
+
[[ -n "$name" ]] || _die "usage: cac env create <name> [-p <proxy>] [-c <version>]"
|
|
1309
1493
|
[[ "$name" =~ ^[a-zA-Z0-9_-]+$ ]] || _die "invalid name '$name' (use alphanumeric, dash, underscore)"
|
|
1310
1494
|
|
|
1311
1495
|
local env_dir="$ENVS_DIR/$name"
|
|
@@ -1314,15 +1498,15 @@ _env_cmd_create() {
|
|
|
1314
1498
|
_timer_start
|
|
1315
1499
|
|
|
1316
1500
|
# Auto-install version (just-in-time, like uv)
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1501
|
+
# No version specified → use latest
|
|
1502
|
+
[[ -z "$claude_ver" ]] && claude_ver="latest"
|
|
1503
|
+
claude_ver=$(_ensure_version_installed "$claude_ver") || exit 1
|
|
1320
1504
|
|
|
1321
1505
|
# Auto-detect proxy protocol
|
|
1322
1506
|
local proxy_url=""
|
|
1323
1507
|
if [[ -n "$proxy" ]]; then
|
|
1324
1508
|
if [[ ! "$proxy" =~ ^(http|https|socks5):// ]]; then
|
|
1325
|
-
printf "Detecting proxy protocol ... "
|
|
1509
|
+
printf " $(_dim "Detecting proxy protocol ...") "
|
|
1326
1510
|
if proxy_url=$(_auto_detect_proxy "$proxy"); then
|
|
1327
1511
|
echo "$(_cyan "$(echo "$proxy_url" | grep -oE '^[a-z]+')")"
|
|
1328
1512
|
else
|
|
@@ -1336,7 +1520,7 @@ _env_cmd_create() {
|
|
|
1336
1520
|
# Geo-detect timezone (single request via proxy)
|
|
1337
1521
|
local tz="America/New_York" lang="en_US.UTF-8"
|
|
1338
1522
|
if [[ -n "$proxy_url" ]]; then
|
|
1339
|
-
printf "Detecting timezone ... "
|
|
1523
|
+
printf " $(_dim "Detecting timezone ...") "
|
|
1340
1524
|
local ip_info
|
|
1341
1525
|
ip_info=$(curl -s --proxy "$proxy_url" --connect-timeout 8 "http://ip-api.com/json/?fields=timezone,countryCode" 2>/dev/null || true)
|
|
1342
1526
|
if [[ -n "$ip_info" ]]; then
|
|
@@ -1363,42 +1547,86 @@ _env_cmd_create() {
|
|
|
1363
1547
|
echo "$env_type" > "$env_dir/type"
|
|
1364
1548
|
mkdir -p "$env_dir/.claude"
|
|
1365
1549
|
|
|
1550
|
+
# Initialize settings.json, statusline, and CLAUDE.md
|
|
1551
|
+
_write_env_settings "$env_dir/.claude"
|
|
1552
|
+
_write_statusline_script "$env_dir/.claude"
|
|
1553
|
+
_write_env_claude_md "$env_dir/.claude" "$name"
|
|
1554
|
+
|
|
1366
1555
|
_generate_client_cert "$name" >/dev/null 2>&1 || true
|
|
1367
1556
|
|
|
1557
|
+
# Auto-activate
|
|
1558
|
+
echo "$name" > "$CAC_DIR/current"
|
|
1559
|
+
rm -f "$CAC_DIR/stopped"
|
|
1560
|
+
if [[ -d "$env_dir/.claude" ]]; then
|
|
1561
|
+
export CLAUDE_CONFIG_DIR="$env_dir/.claude"
|
|
1562
|
+
fi
|
|
1563
|
+
_update_statsig "$(_read "$env_dir/stable_id")" 2>/dev/null || true
|
|
1564
|
+
_update_claude_json_user_id "$(_read "$env_dir/user_id")" 2>/dev/null || true
|
|
1565
|
+
|
|
1368
1566
|
local elapsed; elapsed=$(_timer_elapsed)
|
|
1369
|
-
echo "$(_green_bold "Created") environment $(_cyan "$name") $(_dim "in $elapsed")"
|
|
1370
|
-
[[ -n "$proxy_url" ]] && echo " $(_green "+") proxy: $proxy_url"
|
|
1371
|
-
[[ -n "$claude_ver" ]] && echo " $(_green "+") claude: $(_cyan "$claude_ver")"
|
|
1372
|
-
echo " $(_green "+") type: $env_type"
|
|
1373
1567
|
echo
|
|
1374
|
-
echo "
|
|
1568
|
+
echo " $(_green_bold "Created") $(_bold "$name") $(_dim "in $elapsed")"
|
|
1569
|
+
echo
|
|
1570
|
+
[[ -n "$proxy_url" ]] && echo " $(_green "+") proxy $proxy_url"
|
|
1571
|
+
[[ -n "$claude_ver" ]] && echo " $(_green "+") claude $(_cyan "$claude_ver")"
|
|
1572
|
+
echo " $(_green "+") env $(_dim "${env_dir/#$HOME/~}/.claude/")"
|
|
1573
|
+
echo
|
|
1574
|
+
echo " $(_dim "Environment activated. Run") $(_green "claude") $(_dim "to start.")"
|
|
1575
|
+
echo
|
|
1375
1576
|
}
|
|
1376
1577
|
|
|
1377
1578
|
_env_cmd_ls() {
|
|
1378
1579
|
if [[ ! -d "$ENVS_DIR" ]] || [[ -z "$(ls -A "$ENVS_DIR" 2>/dev/null)" ]]; then
|
|
1379
|
-
echo "$(_dim "
|
|
1580
|
+
echo "$(_dim " No environments yet.")"
|
|
1581
|
+
echo " Run $(_green "cac env create <name>") to get started."
|
|
1380
1582
|
return
|
|
1381
1583
|
fi
|
|
1382
1584
|
|
|
1383
1585
|
local current; current=$(_current_env)
|
|
1384
|
-
local stopped_tag=""
|
|
1385
|
-
[[ -f "$CAC_DIR/stopped" ]] && stopped_tag=" $(_red "[stopped]")"
|
|
1386
1586
|
|
|
1587
|
+
# Collect data first to calculate column widths
|
|
1588
|
+
local names=() versions=() proxies=() paths=()
|
|
1387
1589
|
for env_dir in "$ENVS_DIR"/*/; do
|
|
1388
1590
|
[[ -d "$env_dir" ]] || continue
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
local
|
|
1392
|
-
|
|
1591
|
+
names+=("$(basename "$env_dir")")
|
|
1592
|
+
versions+=("$(_read "$env_dir/version" "system")")
|
|
1593
|
+
local p; p=$(_read "$env_dir/proxy" "")
|
|
1594
|
+
if [[ -n "$p" ]] && [[ "$p" == *"://"*"@"* ]]; then
|
|
1595
|
+
p=$(echo "$p" | sed 's|://[^@]*@|://***@|')
|
|
1596
|
+
fi
|
|
1597
|
+
proxies+=("${p:-—}")
|
|
1598
|
+
local ep="${env_dir}.claude/"
|
|
1599
|
+
paths+=("${ep/#$HOME/~}")
|
|
1600
|
+
done
|
|
1601
|
+
|
|
1602
|
+
# Calculate max widths
|
|
1603
|
+
local max_name=4 max_ver=6 max_proxy=5
|
|
1604
|
+
local i
|
|
1605
|
+
for i in "${!names[@]}"; do
|
|
1606
|
+
local nl=${#names[$i]} vl=${#versions[$i]} pl=${#proxies[$i]}
|
|
1607
|
+
(( nl > max_name )) && max_name=$nl
|
|
1608
|
+
(( vl > max_ver )) && max_ver=$vl
|
|
1609
|
+
(( pl > max_proxy )) && max_proxy=$pl
|
|
1610
|
+
done
|
|
1611
|
+
# Cap proxy column
|
|
1612
|
+
(( max_proxy > 40 )) && max_proxy=40
|
|
1613
|
+
|
|
1614
|
+
# Header
|
|
1615
|
+
printf " $(_dim " %-${max_name}s %-${max_ver}s %-${max_proxy}s %s")" "NAME" "CLAUDE" "PROXY" "ENV"
|
|
1616
|
+
echo
|
|
1617
|
+
|
|
1618
|
+
# Rows
|
|
1619
|
+
for i in "${!names[@]}"; do
|
|
1620
|
+
local name="${names[$i]}"
|
|
1621
|
+
local ver="${versions[$i]}"
|
|
1622
|
+
local proxy="${proxies[$i]}"
|
|
1623
|
+
local epath="${paths[$i]}"
|
|
1393
1624
|
|
|
1394
1625
|
if [[ "$name" == "$current" ]]; then
|
|
1395
|
-
printf "
|
|
1626
|
+
printf " $(_green "▶") $(_bold "%-${max_name}s") $(_cyan "%-${max_ver}s") %-${max_proxy}s $(_dim "%s")\n" "$name" "$ver" "$proxy" "$epath"
|
|
1396
1627
|
else
|
|
1397
|
-
printf "
|
|
1628
|
+
printf " $(_dim "○") %-${max_name}s $(_cyan "%-${max_ver}s") $(_dim "%-${max_proxy}s") $(_dim "%s")\n" "$name" "$ver" "$proxy" "$epath"
|
|
1398
1629
|
fi
|
|
1399
|
-
local details="claude: $(_cyan "$ver") type: $etype"
|
|
1400
|
-
[[ -n "$proxy" ]] && details="proxy: $proxy $details"
|
|
1401
|
-
printf " %s\n" "$details"
|
|
1402
1630
|
done
|
|
1403
1631
|
}
|
|
1404
1632
|
|
|
@@ -1410,7 +1638,7 @@ _env_cmd_rm() {
|
|
|
1410
1638
|
local current; current=$(_current_env)
|
|
1411
1639
|
[[ "$name" != "$current" ]] || _die "cannot remove active environment $(_cyan "'$name'")\n switch to another environment first"
|
|
1412
1640
|
|
|
1413
|
-
rm -rf "$ENVS_DIR/$name"
|
|
1641
|
+
rm -rf "${ENVS_DIR:?}/$name"
|
|
1414
1642
|
echo "$(_green_bold "Removed") environment $(_cyan "$name")"
|
|
1415
1643
|
}
|
|
1416
1644
|
|
|
@@ -1444,37 +1672,115 @@ _env_cmd_activate() {
|
|
|
1444
1672
|
echo "$(_green_bold "Activated") $(_bold "$name") $(_dim "in $elapsed")"
|
|
1445
1673
|
}
|
|
1446
1674
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1675
|
+
_env_cmd_set() {
|
|
1676
|
+
_require_setup
|
|
1677
|
+
|
|
1678
|
+
# Parse: cac env set [name] <key> <value|--remove>
|
|
1679
|
+
# If first arg is a known key, use current env; otherwise treat as env name
|
|
1680
|
+
local name="" key="" value="" remove=false
|
|
1681
|
+
local known_keys="proxy version"
|
|
1682
|
+
|
|
1683
|
+
if [[ $# -lt 1 ]] || [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "help" ]]; then
|
|
1684
|
+
echo
|
|
1685
|
+
echo " $(_bold "cac env set") — modify environment configuration"
|
|
1686
|
+
echo
|
|
1687
|
+
echo " $(_green "set") [name] proxy <url> Set proxy"
|
|
1688
|
+
echo " $(_green "set") [name] proxy --remove Remove proxy"
|
|
1689
|
+
echo " $(_green "set") [name] version <ver|latest> Change Claude version"
|
|
1690
|
+
echo
|
|
1691
|
+
echo " $(_dim "If name is omitted, uses the current active environment.")"
|
|
1692
|
+
echo
|
|
1450
1693
|
return
|
|
1451
1694
|
fi
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1695
|
+
|
|
1696
|
+
# Is first arg a known key or an env name?
|
|
1697
|
+
if echo "$known_keys" | grep -qw "${1:-}"; then
|
|
1698
|
+
name=$(_current_env)
|
|
1699
|
+
[[ -n "$name" ]] || _die "no active environment — specify env name"
|
|
1700
|
+
else
|
|
1701
|
+
name="$1"; shift
|
|
1702
|
+
fi
|
|
1703
|
+
|
|
1704
|
+
_require_env "$name"
|
|
1705
|
+
local env_dir="$ENVS_DIR/$name"
|
|
1706
|
+
|
|
1707
|
+
[[ $# -ge 1 ]] || _die "usage: cac env set [name] <proxy|version|bypass> <value|--remove>"
|
|
1708
|
+
key="$1"; shift
|
|
1709
|
+
|
|
1710
|
+
# Parse value or --remove
|
|
1711
|
+
if [[ "${1:-}" == "--remove" ]]; then
|
|
1712
|
+
remove=true; shift
|
|
1713
|
+
elif [[ $# -ge 1 ]]; then
|
|
1714
|
+
value="$1"; shift
|
|
1715
|
+
fi
|
|
1716
|
+
|
|
1717
|
+
case "$key" in
|
|
1718
|
+
proxy)
|
|
1719
|
+
if [[ "$remove" == "true" ]]; then
|
|
1720
|
+
rm -f "$env_dir/proxy"
|
|
1721
|
+
echo "$(_green_bold "Removed") proxy from $(_bold "$name")"
|
|
1722
|
+
else
|
|
1723
|
+
[[ -n "$value" ]] || _die "usage: cac env set [name] proxy <url|host:port:user:pass>"
|
|
1724
|
+
local proxy_url
|
|
1725
|
+
if [[ ! "$value" =~ ^(http|https|socks5):// ]]; then
|
|
1726
|
+
printf " $(_dim "Detecting proxy protocol ...") "
|
|
1727
|
+
if proxy_url=$(_auto_detect_proxy "$value"); then
|
|
1728
|
+
echo "$(_cyan "$(echo "$proxy_url" | grep -oE '^[a-z]+')")"
|
|
1729
|
+
else
|
|
1730
|
+
echo "$(_yellow "failed, defaulting to http")"
|
|
1731
|
+
fi
|
|
1732
|
+
else
|
|
1733
|
+
proxy_url=$(_parse_proxy "$value")
|
|
1734
|
+
fi
|
|
1735
|
+
echo "$proxy_url" > "$env_dir/proxy"
|
|
1736
|
+
echo "$(_green_bold "Set") proxy for $(_bold "$name") → $proxy_url"
|
|
1737
|
+
fi
|
|
1738
|
+
;;
|
|
1739
|
+
version)
|
|
1740
|
+
[[ "$remove" != "true" ]] || _die "cannot remove version — use 'cac env set $name version latest'"
|
|
1741
|
+
[[ -n "$value" ]] || _die "usage: cac env set [name] version <ver|latest>"
|
|
1742
|
+
local ver
|
|
1743
|
+
ver=$(_ensure_version_installed "$value") || exit 1
|
|
1744
|
+
echo "$ver" > "$env_dir/version"
|
|
1745
|
+
echo "$(_green_bold "Set") version for $(_bold "$name") → $(_cyan "$ver")"
|
|
1746
|
+
;;
|
|
1747
|
+
*)
|
|
1748
|
+
_die "unknown key '$key' — use proxy or version"
|
|
1749
|
+
;;
|
|
1750
|
+
esac
|
|
1457
1751
|
}
|
|
1458
1752
|
|
|
1459
1753
|
cmd_env() {
|
|
1460
1754
|
case "${1:-help}" in
|
|
1461
1755
|
create) _env_cmd_create "${@:2}" ;;
|
|
1756
|
+
set) _env_cmd_set "${@:2}" ;;
|
|
1462
1757
|
ls|list) _env_cmd_ls ;;
|
|
1463
1758
|
rm|remove) _env_cmd_rm "${@:2}" ;;
|
|
1464
1759
|
activate) _env_cmd_activate "${@:2}" ;;
|
|
1465
|
-
|
|
1466
|
-
|
|
1760
|
+
check) cmd_check "${@:2}" ;;
|
|
1761
|
+
deactivate) echo "$(_yellow "warning:") deactivate has been removed — switch with 'cac <name>' or uninstall with 'cac self delete'" >&2 ;;
|
|
1467
1762
|
help|-h|--help)
|
|
1468
|
-
echo "$(_bold "cac env") — environment management"
|
|
1469
1763
|
echo
|
|
1470
|
-
echo " $(_bold "
|
|
1471
|
-
echo
|
|
1472
|
-
echo "
|
|
1473
|
-
echo "
|
|
1474
|
-
echo "
|
|
1475
|
-
echo "
|
|
1764
|
+
echo " $(_bold "cac env") — environment management"
|
|
1765
|
+
echo
|
|
1766
|
+
echo " $(_green "create") <name> [-p proxy] [-c ver]"
|
|
1767
|
+
echo " $(_green "set") [name] proxy <url> Set proxy"
|
|
1768
|
+
echo " $(_green "set") [name] proxy --remove Remove proxy"
|
|
1769
|
+
echo " $(_green "set") [name] version <ver|latest> Change Claude version"
|
|
1770
|
+
echo " $(_green "ls") List all environments"
|
|
1771
|
+
echo " $(_green "rm") <name> Remove an environment"
|
|
1772
|
+
echo " $(_green "check") Verify current environment"
|
|
1773
|
+
echo " $(_green "cac") <name> Switch environment"
|
|
1774
|
+
echo
|
|
1775
|
+
;;
|
|
1776
|
+
*)
|
|
1777
|
+
# If first arg is an existing env name, treat as: cac env set <name> ...
|
|
1778
|
+
if [[ -d "$ENVS_DIR/$1" ]] && [[ $# -ge 2 ]]; then
|
|
1779
|
+
_env_cmd_set "$@"
|
|
1780
|
+
else
|
|
1781
|
+
_die "unknown: cac env $1"
|
|
1782
|
+
fi
|
|
1476
1783
|
;;
|
|
1477
|
-
*) _die "unknown: cac env $1" ;;
|
|
1478
1784
|
esac
|
|
1479
1785
|
}
|
|
1480
1786
|
|
|
@@ -1505,8 +1811,8 @@ _relay_start() {
|
|
|
1505
1811
|
disown
|
|
1506
1812
|
|
|
1507
1813
|
# 等待 relay 就绪
|
|
1508
|
-
local
|
|
1509
|
-
for
|
|
1814
|
+
local _i
|
|
1815
|
+
for _i in {1..30}; do
|
|
1510
1816
|
(echo >/dev/tcp/127.0.0.1/$port) 2>/dev/null && break
|
|
1511
1817
|
sleep 0.1
|
|
1512
1818
|
done
|
|
@@ -1527,8 +1833,8 @@ _relay_stop() {
|
|
|
1527
1833
|
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
1528
1834
|
kill "$pid" 2>/dev/null
|
|
1529
1835
|
# 等待进程退出
|
|
1530
|
-
local
|
|
1531
|
-
for
|
|
1836
|
+
local _i
|
|
1837
|
+
for _i in {1..20}; do
|
|
1532
1838
|
kill -0 "$pid" 2>/dev/null || break
|
|
1533
1839
|
sleep 0.1
|
|
1534
1840
|
done
|
|
@@ -1702,18 +2008,14 @@ cmd_check() {
|
|
|
1702
2008
|
|
|
1703
2009
|
local current; current=$(_current_env)
|
|
1704
2010
|
|
|
1705
|
-
if [[ -f "$CAC_DIR/stopped" ]]; then
|
|
1706
|
-
echo "$(_red "✗") cac 已停用 — 运行 'cac <name>' 恢复"
|
|
1707
|
-
return
|
|
1708
|
-
fi
|
|
1709
2011
|
if [[ -z "$current" ]]; then
|
|
1710
|
-
echo "
|
|
2012
|
+
echo "error: no active environment — run $(_green "cac env create <name>")" >&2; exit 1
|
|
1711
2013
|
fi
|
|
1712
2014
|
|
|
1713
2015
|
local env_dir="$ENVS_DIR/$current"
|
|
1714
2016
|
local proxy; proxy=$(_read "$env_dir/proxy" "")
|
|
1715
2017
|
|
|
1716
|
-
#
|
|
2018
|
+
# Resolve version
|
|
1717
2019
|
local ver; ver=$(_read "$env_dir/version" "")
|
|
1718
2020
|
if [[ -z "$ver" ]] || [[ "$ver" == "system" ]]; then
|
|
1719
2021
|
local _real; _real=$(_read "$CAC_DIR/real_claude" "")
|
|
@@ -1725,80 +2027,27 @@ cmd_check() {
|
|
|
1725
2027
|
fi
|
|
1726
2028
|
|
|
1727
2029
|
local problems=()
|
|
1728
|
-
local summary_parts=()
|
|
1729
|
-
|
|
1730
|
-
# ── wrapper 检查 ──
|
|
1731
|
-
local claude_path; claude_path="$(command -v claude 2>/dev/null || true)"
|
|
1732
|
-
if [[ -z "$claude_path" ]] || [[ "$(readlink -f "$claude_path" 2>/dev/null || echo "$claude_path")" != *"/.cac/bin/claude"* ]]; then
|
|
1733
|
-
if [[ "$claude_path" != *"/.cac/bin/claude" ]]; then
|
|
1734
|
-
problems+=("claude 未指向 wrapper — 运行 source ~/.zshrc 或重开终端")
|
|
1735
|
-
fi
|
|
1736
|
-
fi
|
|
1737
2030
|
|
|
1738
|
-
# ──
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
problems+=("代理不通: $proxy")
|
|
1743
|
-
else
|
|
1744
|
-
proxy_ip=$(curl -s --proxy "$proxy" --connect-timeout 8 https://api.ipify.org 2>/dev/null || true)
|
|
1745
|
-
if [[ -n "$proxy_ip" ]]; then
|
|
1746
|
-
summary_parts+=("出口 $proxy_ip")
|
|
1747
|
-
else
|
|
1748
|
-
problems+=("出口 IP 获取失败")
|
|
1749
|
-
fi
|
|
1750
|
-
fi
|
|
2031
|
+
# ── header (neutral, no pass/fail yet) ──
|
|
2032
|
+
echo
|
|
2033
|
+
echo " $(_bold "$current") $(_dim "(claude $ver)")"
|
|
2034
|
+
echo
|
|
1751
2035
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
local
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
if [[ "$os" == "macos" ]]; then
|
|
1759
|
-
running=$(ps aux 2>/dev/null | grep -iE "$tun_procs" | grep -v grep || true)
|
|
2036
|
+
# ── wrapper check (instant) ──
|
|
2037
|
+
local claude_path; claude_path="$(command -v claude 2>/dev/null || true)"
|
|
2038
|
+
if [[ -z "$claude_path" ]] || [[ "$claude_path" != *"/.cac/bin/claude" ]]; then
|
|
2039
|
+
local _rc; _rc=$(_detect_rc_file)
|
|
2040
|
+
if [[ -n "$_rc" ]] && grep -q '# >>> cac' "$_rc" 2>/dev/null; then
|
|
2041
|
+
echo " $(_green "✓") wrapper configured in ${_rc/#$HOME/~}"
|
|
1760
2042
|
else
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
[[ -n "$running" ]] && has_conflict=true
|
|
1764
|
-
if [[ "$os" == "macos" ]]; then
|
|
1765
|
-
local tun_count; tun_count=$(ifconfig 2>/dev/null | grep -cE '^utun[0-9]+' || echo 0)
|
|
1766
|
-
[[ "$tun_count" -gt 3 ]] && has_conflict=true
|
|
1767
|
-
elif [[ "$os" == "linux" ]]; then
|
|
1768
|
-
ip link show tun0 >/dev/null 2>&1 && has_conflict=true
|
|
1769
|
-
fi
|
|
1770
|
-
|
|
1771
|
-
if [[ "$has_conflict" == "true" ]]; then
|
|
1772
|
-
# 测试 relay
|
|
1773
|
-
local relay_ok=false
|
|
1774
|
-
if _relay_is_running 2>/dev/null; then
|
|
1775
|
-
local rport; rport=$(_read "$CAC_DIR/relay.port" "")
|
|
1776
|
-
local relay_ip; relay_ip=$(curl -s --proxy "http://127.0.0.1:$rport" --connect-timeout 8 https://api.ipify.org 2>/dev/null || true)
|
|
1777
|
-
[[ -n "$relay_ip" ]] && relay_ok=true
|
|
1778
|
-
elif [[ -f "$CAC_DIR/relay.js" ]]; then
|
|
1779
|
-
local _test_env; _test_env=$(_current_env)
|
|
1780
|
-
if _relay_start "$_test_env" 2>/dev/null; then
|
|
1781
|
-
local rport; rport=$(_read "$CAC_DIR/relay.port" "")
|
|
1782
|
-
local relay_ip; relay_ip=$(curl -s --proxy "http://127.0.0.1:$rport" --connect-timeout 8 https://api.ipify.org 2>/dev/null || true)
|
|
1783
|
-
_relay_stop 2>/dev/null || true
|
|
1784
|
-
[[ -n "$relay_ip" ]] && relay_ok=true
|
|
1785
|
-
fi
|
|
1786
|
-
fi
|
|
1787
|
-
|
|
1788
|
-
if [[ "$relay_ok" == "true" ]]; then
|
|
1789
|
-
summary_parts+=("TUN 冲突已绕过")
|
|
1790
|
-
else
|
|
1791
|
-
local proxy_hp; proxy_hp=$(_proxy_host_port "$proxy")
|
|
1792
|
-
local proxy_host="${proxy_hp%%:*}"
|
|
1793
|
-
problems+=("代理冲突:需在代理软件中为 $proxy_host 添加 DIRECT 规则")
|
|
1794
|
-
fi
|
|
1795
|
-
fi
|
|
2043
|
+
_write_path_to_rc "$_rc" >/dev/null 2>&1 || true
|
|
2044
|
+
echo " $(_green "✓") wrapper $(_dim "added to ${_rc/#$HOME/~}")"
|
|
1796
2045
|
fi
|
|
1797
2046
|
else
|
|
1798
|
-
|
|
2047
|
+
echo " $(_green "✓") wrapper active"
|
|
1799
2048
|
fi
|
|
1800
2049
|
|
|
1801
|
-
# ──
|
|
2050
|
+
# ── telemetry shield (instant) ──
|
|
1802
2051
|
local wrapper_file="$CAC_DIR/bin/claude"
|
|
1803
2052
|
local wrapper_content=""
|
|
1804
2053
|
[[ -f "$wrapper_file" ]] && wrapper_content=$(<"$wrapper_file")
|
|
@@ -1814,43 +2063,125 @@ cmd_check() {
|
|
|
1814
2063
|
done
|
|
1815
2064
|
|
|
1816
2065
|
if [[ "$env_ok" -eq "$env_total" ]]; then
|
|
1817
|
-
|
|
2066
|
+
echo " $(_green "✓") telemetry ${env_ok}/${env_total} blocked"
|
|
1818
2067
|
else
|
|
1819
|
-
|
|
2068
|
+
echo " $(_red "✗") telemetry ${env_ok}/${env_total} blocked"
|
|
2069
|
+
problems+=("telemetry shield ${env_ok}/${env_total}")
|
|
1820
2070
|
fi
|
|
1821
2071
|
|
|
1822
|
-
# ──
|
|
2072
|
+
# ── network check (slow — streaming output) ──
|
|
2073
|
+
local proxy_ip=""
|
|
2074
|
+
if [[ -n "$proxy" ]]; then
|
|
2075
|
+
if ! _proxy_reachable "$proxy"; then
|
|
2076
|
+
echo " $(_red "✗") proxy unreachable"
|
|
2077
|
+
problems+=("proxy unreachable: $proxy")
|
|
2078
|
+
else
|
|
2079
|
+
# Fast retry with dots: each attempt adds a dot
|
|
2080
|
+
local _ip_url _dots=""
|
|
2081
|
+
local _urls="https://api.ip.sb/ip https://ip.3322.net https://api.ipify.org https://ipinfo.io/ip https://api.ip.sb/ip"
|
|
2082
|
+
for _ip_url in $_urls; do
|
|
2083
|
+
_dots="${_dots}."
|
|
2084
|
+
printf "\r · exit IP $(_dim "detecting${_dots}")"
|
|
2085
|
+
proxy_ip=$(curl --proxy "$proxy" --connect-timeout 3 --max-time 6 "$_ip_url" 2>/dev/null || true)
|
|
2086
|
+
[[ "$proxy_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && break
|
|
2087
|
+
proxy_ip=""
|
|
2088
|
+
done
|
|
2089
|
+
# Overwrite the "detecting..." line
|
|
2090
|
+
if [[ -n "$proxy_ip" ]]; then
|
|
2091
|
+
printf "\r $(_green "✓") exit IP $(_cyan "$proxy_ip")\033[K\n"
|
|
2092
|
+
else
|
|
2093
|
+
printf "\r $(_green "✓") exit IP $(_dim "run again to detect exit IP")\033[K\n"
|
|
2094
|
+
fi
|
|
2095
|
+
|
|
2096
|
+
# TUN conflict detection
|
|
2097
|
+
if [[ -n "$proxy_ip" ]]; then
|
|
2098
|
+
local os; os=$(_detect_os)
|
|
2099
|
+
local has_conflict=false
|
|
2100
|
+
local tun_procs="clash|mihomo|sing-box|surge|shadowrocket|v2ray|xray|hysteria|tuic|nekoray"
|
|
2101
|
+
local running
|
|
2102
|
+
if [[ "$os" == "macos" ]]; then
|
|
2103
|
+
running=$(ps aux 2>/dev/null | grep -iE "$tun_procs" | grep -v grep || true)
|
|
2104
|
+
else
|
|
2105
|
+
running=$(ps -eo comm 2>/dev/null | grep -iE "$tun_procs" || true)
|
|
2106
|
+
fi
|
|
2107
|
+
[[ -n "$running" ]] && has_conflict=true
|
|
2108
|
+
if [[ "$os" == "macos" ]]; then
|
|
2109
|
+
local tun_count; tun_count=$(ifconfig 2>/dev/null | grep -cE '^utun[0-9]+' || echo 0)
|
|
2110
|
+
[[ "$tun_count" -gt 3 ]] && has_conflict=true
|
|
2111
|
+
elif [[ "$os" == "linux" ]]; then
|
|
2112
|
+
ip link show tun0 >/dev/null 2>&1 && has_conflict=true
|
|
2113
|
+
fi
|
|
2114
|
+
|
|
2115
|
+
if [[ "$has_conflict" == "true" ]]; then
|
|
2116
|
+
local relay_ok=false
|
|
2117
|
+
if _relay_is_running 2>/dev/null; then
|
|
2118
|
+
local rport; rport=$(_read "$CAC_DIR/relay.port" "")
|
|
2119
|
+
local relay_ip; relay_ip=$(curl --proxy "http://127.0.0.1:$rport" --connect-timeout 8 --max-time 12 https://api.ipify.org 2>/dev/null || true)
|
|
2120
|
+
[[ -n "$relay_ip" ]] && relay_ok=true
|
|
2121
|
+
elif [[ -f "$CAC_DIR/relay.js" ]]; then
|
|
2122
|
+
local _test_env; _test_env=$(_current_env)
|
|
2123
|
+
if _relay_start "$_test_env" 2>/dev/null; then
|
|
2124
|
+
local rport; rport=$(_read "$CAC_DIR/relay.port" "")
|
|
2125
|
+
local relay_ip; relay_ip=$(curl --proxy "http://127.0.0.1:$rport" --connect-timeout 8 --max-time 12 https://api.ipify.org 2>/dev/null || true)
|
|
2126
|
+
_relay_stop 2>/dev/null || true
|
|
2127
|
+
[[ -n "$relay_ip" ]] && relay_ok=true
|
|
2128
|
+
fi
|
|
2129
|
+
fi
|
|
2130
|
+
|
|
2131
|
+
if [[ "$relay_ok" == "true" ]]; then
|
|
2132
|
+
echo " $(_green "✓") TUN relay bypass active"
|
|
2133
|
+
else
|
|
2134
|
+
local proxy_hp; proxy_hp=$(_proxy_host_port "$proxy")
|
|
2135
|
+
local proxy_host="${proxy_hp%%:*}"
|
|
2136
|
+
echo " $(_red "✗") TUN conflict — add DIRECT rule for $proxy_host"
|
|
2137
|
+
problems+=("TUN conflict: add DIRECT rule for $proxy_host in proxy software")
|
|
2138
|
+
fi
|
|
2139
|
+
fi
|
|
2140
|
+
fi
|
|
2141
|
+
fi
|
|
2142
|
+
else
|
|
2143
|
+
echo " $(_green "✓") mode API Key (no proxy)"
|
|
2144
|
+
fi
|
|
2145
|
+
|
|
2146
|
+
# ── summary ──
|
|
2147
|
+
echo
|
|
1823
2148
|
if [[ ${#problems[@]} -eq 0 ]]; then
|
|
1824
|
-
echo "$(_green "✓")
|
|
1825
|
-
echo " $(IFS=' | '; echo "${summary_parts[*]}")"
|
|
2149
|
+
echo " $(_green "✓") all good"
|
|
1826
2150
|
else
|
|
1827
|
-
echo "$(_red "✗") $(_bold "$current") (claude $(_cyan "$ver")) — 发现 ${#problems[@]} 个问题"
|
|
1828
2151
|
for p in "${problems[@]}"; do
|
|
1829
2152
|
echo " $(_red "✗") $p"
|
|
1830
2153
|
done
|
|
1831
2154
|
fi
|
|
2155
|
+
echo
|
|
1832
2156
|
|
|
1833
|
-
# ──
|
|
2157
|
+
# ── verbose mode ──
|
|
1834
2158
|
if [[ "$verbose" == "true" ]]; then
|
|
2159
|
+
echo " $(_bold "Details")"
|
|
2160
|
+
echo " $(_dim "UUID") $(_read "$env_dir/uuid")"
|
|
2161
|
+
echo " $(_dim "stable_id") $(_read "$env_dir/stable_id")"
|
|
2162
|
+
echo " $(_dim "user_id") $(_read "$env_dir/user_id" "—")"
|
|
2163
|
+
echo " $(_dim "TZ") $(_read "$env_dir/tz" "—")"
|
|
2164
|
+
echo " $(_dim "LANG") $(_read "$env_dir/lang" "—")"
|
|
2165
|
+
echo " $(_dim "env") ${env_dir/#$HOME/~}/.claude/"
|
|
1835
2166
|
echo
|
|
1836
|
-
echo "
|
|
1837
|
-
echo " stable_id : $(_read "$env_dir/stable_id")"
|
|
1838
|
-
echo " user_id : $(_read "$env_dir/user_id" "—")"
|
|
1839
|
-
echo " TZ : $(_read "$env_dir/tz" "—")"
|
|
1840
|
-
echo " LANG : $(_read "$env_dir/lang" "—")"
|
|
1841
|
-
echo " 遥测屏蔽 : ${env_ok}/${env_total}"
|
|
2167
|
+
echo " $(_bold "Telemetry") ${env_ok}/${env_total}"
|
|
1842
2168
|
for var in "${env_vars[@]}"; do
|
|
1843
|
-
|
|
1844
|
-
|
|
2169
|
+
if [[ "$wrapper_content" == *"$var"* ]]; then
|
|
2170
|
+
printf " $(_green "✓") %s\n" "$var"
|
|
2171
|
+
else
|
|
2172
|
+
printf " $(_red "✗") %s\n" "$var"
|
|
2173
|
+
fi
|
|
1845
2174
|
done
|
|
1846
|
-
|
|
2175
|
+
echo
|
|
2176
|
+
printf " $(_bold "DNS block") "
|
|
1847
2177
|
if [[ -f "$CAC_DIR/cac-dns-guard.js" ]]; then
|
|
1848
2178
|
_check_dns_block "statsig.anthropic.com"
|
|
1849
2179
|
else
|
|
1850
2180
|
echo "$(_red "✗")"
|
|
1851
2181
|
fi
|
|
1852
|
-
printf "
|
|
2182
|
+
printf " $(_bold "mTLS") "
|
|
1853
2183
|
_check_mtls "$env_dir"
|
|
2184
|
+
echo
|
|
1854
2185
|
fi
|
|
1855
2186
|
}
|
|
1856
2187
|
|
|
@@ -1959,7 +2290,7 @@ _claude_cmd_install() {
|
|
|
1959
2290
|
|
|
1960
2291
|
mkdir -p "$VERSIONS_DIR"
|
|
1961
2292
|
if _download_version "$ver"; then
|
|
1962
|
-
|
|
2293
|
+
_update_latest
|
|
1963
2294
|
echo
|
|
1964
2295
|
echo " Bind to environment: $(_cyan "cac env create <name> -c $ver")"
|
|
1965
2296
|
fi
|
|
@@ -1973,35 +2304,34 @@ _claude_cmd_uninstall() {
|
|
|
1973
2304
|
local count; count=$(_envs_using_version "$ver")
|
|
1974
2305
|
[[ "$count" -eq 0 ]] || _die "version $(_cyan "$ver") in use by $count environment(s)"
|
|
1975
2306
|
|
|
1976
|
-
rm -rf "$VERSIONS_DIR/$ver"
|
|
2307
|
+
rm -rf "${VERSIONS_DIR:?}/$ver"
|
|
2308
|
+
_update_latest
|
|
1977
2309
|
echo "$(_green_bold "Uninstalled") Claude Code $(_cyan "$ver")"
|
|
1978
2310
|
}
|
|
1979
2311
|
|
|
1980
2312
|
_claude_cmd_ls() {
|
|
1981
|
-
|
|
1982
|
-
echo
|
|
1983
|
-
|
|
2313
|
+
_update_latest 2>/dev/null || true
|
|
1984
2314
|
if [[ ! -d "$VERSIONS_DIR" ]] || [[ -z "$(ls -A "$VERSIONS_DIR" 2>/dev/null)" ]]; then
|
|
1985
|
-
echo "
|
|
2315
|
+
echo "$(_dim " No versions installed.")"
|
|
2316
|
+
echo " Run $(_green "cac claude install") to get started."
|
|
1986
2317
|
return
|
|
1987
2318
|
fi
|
|
1988
2319
|
|
|
1989
2320
|
local latest; latest=$(_read "$VERSIONS_DIR/.latest" "")
|
|
2321
|
+
|
|
2322
|
+
printf " $(_dim "%-12s %-8s %s")\n" "VERSION" "STATUS" "ENVIRONMENTS"
|
|
1990
2323
|
for ver_dir in "$VERSIONS_DIR"/*/; do
|
|
1991
2324
|
[[ -d "$ver_dir" ]] || continue
|
|
1992
2325
|
local ver; ver=$(basename "$ver_dir")
|
|
1993
|
-
local
|
|
2326
|
+
local status=""; [[ "$ver" == "$latest" ]] && status="latest"
|
|
1994
2327
|
local count; count=$(_envs_using_version "$ver")
|
|
1995
|
-
local usage=""; [[ "$count" -gt 0 ]] && usage="
|
|
1996
|
-
|
|
2328
|
+
local usage="—"; [[ "$count" -gt 0 ]] && usage="$count env(s)"
|
|
2329
|
+
if [[ -n "$status" ]]; then
|
|
2330
|
+
printf " $(_cyan "%-12s") $(_green "%-8s") %s\n" "$ver" "$status" "$usage"
|
|
2331
|
+
else
|
|
2332
|
+
printf " $(_cyan "%-12s") $(_dim "%-8s") %s\n" "$ver" "—" "$usage"
|
|
2333
|
+
fi
|
|
1997
2334
|
done
|
|
1998
|
-
|
|
1999
|
-
local sys; sys=$(_read "$CAC_DIR/real_claude" "")
|
|
2000
|
-
if [[ -n "$sys" ]] && [[ -x "$sys" ]]; then
|
|
2001
|
-
local sv; sv=$("$sys" --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "?")
|
|
2002
|
-
echo
|
|
2003
|
-
echo " System: $(_cyan "$sys") ($(_dim "$sv"))"
|
|
2004
|
-
fi
|
|
2005
2335
|
}
|
|
2006
2336
|
|
|
2007
2337
|
_claude_cmd_pin() {
|
|
@@ -2136,6 +2466,7 @@ _dk_init() {
|
|
|
2136
2466
|
}
|
|
2137
2467
|
|
|
2138
2468
|
_dk_load_env() {
|
|
2469
|
+
# shellcheck disable=SC1090 # dynamic env file path
|
|
2139
2470
|
[[ -f "$_dk_env_file" ]] && set -a && source "$_dk_env_file" && set +a
|
|
2140
2471
|
}
|
|
2141
2472
|
|
|
@@ -2182,6 +2513,7 @@ _dk_compose_files() {
|
|
|
2182
2513
|
|
|
2183
2514
|
_dk_compose() {
|
|
2184
2515
|
local files
|
|
2516
|
+
# shellcheck disable=SC2207 # intentional word splitting
|
|
2185
2517
|
files=($(_dk_compose_files))
|
|
2186
2518
|
docker compose "${files[@]}" "$@"
|
|
2187
2519
|
}
|
|
@@ -2615,7 +2947,23 @@ cmd_delete() {
|
|
|
2615
2947
|
|
|
2616
2948
|
_remove_path_from_rc "$rc_file"
|
|
2617
2949
|
|
|
2950
|
+
# 停止 relay 进程和路由
|
|
2618
2951
|
if [[ -d "$CAC_DIR" ]]; then
|
|
2952
|
+
_relay_stop 2>/dev/null || true
|
|
2953
|
+
|
|
2954
|
+
# 停止 docker port-forward 进程
|
|
2955
|
+
if [[ -d /tmp/cac-docker-ports ]]; then
|
|
2956
|
+
for _pf in /tmp/cac-docker-ports/*.pid; do
|
|
2957
|
+
[[ -f "$_pf" ]] || continue
|
|
2958
|
+
kill "$(cat "$_pf")" 2>/dev/null || true
|
|
2959
|
+
rm -f "$_pf"
|
|
2960
|
+
done
|
|
2961
|
+
echo " ✓ 已停止 docker port-forward 进程"
|
|
2962
|
+
fi
|
|
2963
|
+
|
|
2964
|
+
# 兜底:清理可能残留的 relay 孤儿进程
|
|
2965
|
+
pkill -f "node.*\.cac/relay\.js" 2>/dev/null || true
|
|
2966
|
+
|
|
2619
2967
|
rm -rf "$CAC_DIR"
|
|
2620
2968
|
echo " ✓ 已删除 $CAC_DIR"
|
|
2621
2969
|
else
|
|
@@ -2650,60 +2998,47 @@ cmd_delete() {
|
|
|
2650
2998
|
# ── cmd: version ───────────────────────────────────────────────
|
|
2651
2999
|
|
|
2652
3000
|
cmd_version() {
|
|
2653
|
-
|
|
2654
|
-
method=$(_install_method)
|
|
2655
|
-
|
|
2656
|
-
local method_label
|
|
2657
|
-
case "$method" in
|
|
2658
|
-
npm) method_label="npm (Node)" ;;
|
|
2659
|
-
bash) method_label="bash (Shell Script)" ;;
|
|
2660
|
-
*) method_label="unknown" ;;
|
|
2661
|
-
esac
|
|
2662
|
-
|
|
2663
|
-
echo "cac $CAC_VERSION"
|
|
2664
|
-
echo "安装方式: $method_label"
|
|
3001
|
+
echo " $(_bold "cac") $(_cyan "$CAC_VERSION")"
|
|
2665
3002
|
}
|
|
2666
3003
|
|
|
2667
3004
|
# ━━━ cmd_help.sh ━━━
|
|
2668
3005
|
# ── cmd: help ──────────────────────────────────────────────────
|
|
2669
3006
|
|
|
2670
3007
|
cmd_help() {
|
|
2671
|
-
|
|
2672
|
-
$(_bold "cac") — Isolate, protect, and manage your Claude Code
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
$(
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
$(
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
$(
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
cac env check
|
|
2706
|
-
EOF
|
|
3008
|
+
echo
|
|
3009
|
+
echo " $(_bold "cac") $(_dim "$CAC_VERSION") — Isolate, protect, and manage your Claude Code"
|
|
3010
|
+
echo
|
|
3011
|
+
|
|
3012
|
+
echo " $(_bold "Environment")"
|
|
3013
|
+
echo " $(_green "cac env create") <name> [-p proxy] [-c ver]"
|
|
3014
|
+
echo " $(_green "cac env set") [name] <key> <value> Modify environment"
|
|
3015
|
+
echo " $(_green "cac env ls") List all environments"
|
|
3016
|
+
echo " $(_green "cac env rm") <name> Remove an environment"
|
|
3017
|
+
echo " $(_green "cac env check") Verify current environment"
|
|
3018
|
+
echo " $(_green "cac") <name> Switch environment"
|
|
3019
|
+
echo
|
|
3020
|
+
|
|
3021
|
+
echo " $(_bold "Version")"
|
|
3022
|
+
echo " $(_green "cac claude install") [latest|ver] Install Claude Code"
|
|
3023
|
+
echo " $(_green "cac claude ls") List installed versions"
|
|
3024
|
+
echo " $(_green "cac claude pin") <ver> Pin env to a version"
|
|
3025
|
+
echo " $(_green "cac claude uninstall") <ver> Remove a version"
|
|
3026
|
+
echo
|
|
3027
|
+
|
|
3028
|
+
echo " $(_bold "Self")"
|
|
3029
|
+
echo " $(_green "cac self update") Update cac"
|
|
3030
|
+
echo " $(_green "cac self delete") Uninstall cac completely"
|
|
3031
|
+
echo
|
|
3032
|
+
|
|
3033
|
+
echo " $(_bold "Docker")"
|
|
3034
|
+
echo " $(_green "cac docker") setup|start|enter|check|port|stop"
|
|
3035
|
+
echo
|
|
3036
|
+
|
|
3037
|
+
echo " $(_dim "Examples:")"
|
|
3038
|
+
echo " $(_dim "cac env create work -p 1.2.3.4:1080:u:p -c 2.1.81")"
|
|
3039
|
+
echo " $(_dim "cac env create personal")"
|
|
3040
|
+
echo " $(_dim "cac work")"
|
|
3041
|
+
echo
|
|
2707
3042
|
}
|
|
2708
3043
|
|
|
2709
3044
|
# ━━━ main.sh ━━━
|
|
@@ -2723,7 +3058,7 @@ case "$1" in
|
|
|
2723
3058
|
add) echo "$(_yellow "warning:") 'cac add' → 'cac env create <name> -p <proxy>'" >&2; exit 1 ;;
|
|
2724
3059
|
setup) echo "$(_yellow "warning:") 'cac setup' is no longer needed — cac auto-initializes" >&2 ;;
|
|
2725
3060
|
check) echo "$(_yellow "warning:") 'cac check' → 'cac env check'" >&2; cmd_check ;;
|
|
2726
|
-
stop) echo "$(_yellow "warning:") 'cac stop'
|
|
3061
|
+
stop) echo "$(_yellow "warning:") 'cac stop' has been removed — switch with 'cac <name>'" >&2 ;;
|
|
2727
3062
|
resume|-c) echo "$(_yellow "warning:") 'cac resume' removed — use 'cac env activate <name>'" >&2; exit 1 ;;
|
|
2728
3063
|
relay) echo "$(_yellow "warning:") relay is now automatic (TUN auto-detected)" >&2 ;;
|
|
2729
3064
|
delete|uninstall) echo "$(_yellow "warning:") 'cac delete' → 'cac self delete'" >&2; cmd_delete ;;
|