claude-cac 1.5.4 → 1.5.6
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/cac +144 -25
- package/package.json +1 -1
package/cac
CHANGED
|
@@ -11,7 +11,7 @@ VERSIONS_DIR="$CAC_DIR/versions"
|
|
|
11
11
|
# ── utils: colors, read/write, UUID, proxy parsing ───────────────────────
|
|
12
12
|
|
|
13
13
|
# shellcheck disable=SC2034 # used in build-concatenated cac script
|
|
14
|
-
CAC_VERSION="1.5.
|
|
14
|
+
CAC_VERSION="1.5.6"
|
|
15
15
|
|
|
16
16
|
_read() { [[ -f "$1" ]] && tr -d '[:space:]' < "$1" || echo "${2:-}"; }
|
|
17
17
|
_die() { printf '%b\n' "$(_red "error:") $*" >&2; exit 1; }
|
|
@@ -58,6 +58,19 @@ _gen_uuid() {
|
|
|
58
58
|
_new_uuid() { _gen_uuid | tr '[:lower:]' '[:upper:]'; }
|
|
59
59
|
_new_user_id() { python3 -c "import os; print(os.urandom(32).hex())" || _die "python3 required"; }
|
|
60
60
|
_new_machine_id() { _gen_uuid | tr -d '-' | tr '[:upper:]' '[:lower:]'; }
|
|
61
|
+
_new_hostname_suffix() { _gen_uuid | tr -d '-' | tr '[:lower:]' '[:upper:]' | cut -c1-5; }
|
|
62
|
+
_detect_hostname_platform() {
|
|
63
|
+
local os; os=$(_detect_os)
|
|
64
|
+
if [[ "$os" == "linux" ]]; then
|
|
65
|
+
if [[ -n "${WSL_DISTRO_NAME:-}" ]] || [[ -n "${WSL_INTEROP:-}" ]] || \
|
|
66
|
+
grep -qi microsoft /proc/sys/kernel/osrelease 2>/dev/null || \
|
|
67
|
+
uname -r 2>/dev/null | grep -qi microsoft; then
|
|
68
|
+
echo "windows"
|
|
69
|
+
return
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
echo "$os"
|
|
73
|
+
}
|
|
61
74
|
_new_hostname() {
|
|
62
75
|
local -a _first_names=(
|
|
63
76
|
"James" "John" "Robert" "Michael" "William" "David" "Richard" "Joseph"
|
|
@@ -68,10 +81,25 @@ _new_hostname() {
|
|
|
68
81
|
"Liam" "Noah" "Oliver" "Elijah" "Lucas" "Mason" "Ethan" "Aiden"
|
|
69
82
|
"Alex" "Ryan" "Tyler" "Jordan" "Taylor" "Morgan" "Casey" "Riley"
|
|
70
83
|
)
|
|
71
|
-
local -a _models=("MacBook-Pro" "MacBook-Air" "MacBook-Pro" "MacBook-Pro")
|
|
72
84
|
local _name="${_first_names[$((RANDOM % ${#_first_names[@]}))]}"
|
|
73
|
-
local
|
|
74
|
-
|
|
85
|
+
local _platform; _platform=$(_detect_hostname_platform)
|
|
86
|
+
case "$_platform" in
|
|
87
|
+
macos)
|
|
88
|
+
local -a _models=("MacBook-Pro" "MacBook-Air" "MacBook-Pro" "MacBook-Pro")
|
|
89
|
+
local _model="${_models[$((RANDOM % ${#_models[@]}))]}"
|
|
90
|
+
echo "${_name}s-${_model}.local"
|
|
91
|
+
;;
|
|
92
|
+
windows)
|
|
93
|
+
local -a _prefixes=("DESKTOP" "LAPTOP")
|
|
94
|
+
local _prefix="${_prefixes[$((RANDOM % ${#_prefixes[@]}))]}"
|
|
95
|
+
echo "${_prefix}-$(_new_hostname_suffix)"
|
|
96
|
+
;;
|
|
97
|
+
*)
|
|
98
|
+
local -a _devices=("desktop" "laptop" "workstation" "thinkpad")
|
|
99
|
+
local _device="${_devices[$((RANDOM % ${#_devices[@]}))]}"
|
|
100
|
+
echo "$(printf '%s' "$_name" | tr '[:upper:]' '[:lower:]')-${_device}"
|
|
101
|
+
;;
|
|
102
|
+
esac
|
|
75
103
|
}
|
|
76
104
|
_new_mac() { printf '02:%02x:%02x:%02x:%02x:%02x' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)); }
|
|
77
105
|
_new_git_remote() { echo "https://github.com/user-$(_gen_uuid | cut -d- -f1)/project-$(_gen_uuid | cut -d- -f2).git"; }
|
|
@@ -86,11 +114,30 @@ _get_real_cmd() {
|
|
|
86
114
|
}
|
|
87
115
|
|
|
88
116
|
# host:port:user:pass → http://user:pass@host:port
|
|
89
|
-
#
|
|
117
|
+
# socks5://host:port:user:pass → socks5://user:pass@host:port
|
|
118
|
+
# or pass a standard URL directly (http://, https://, socks5://)
|
|
90
119
|
_parse_proxy() {
|
|
91
120
|
local raw="$1"
|
|
92
|
-
|
|
121
|
+
local proto rest
|
|
122
|
+
|
|
123
|
+
[[ -n "$raw" ]] || return 0
|
|
124
|
+
|
|
125
|
+
# Normalize protocol-prefixed legacy form:
|
|
126
|
+
# socks5://host:port:user:pass -> socks5://user:pass@host:port
|
|
93
127
|
if [[ "$raw" =~ ^(http|https|socks5):// ]]; then
|
|
128
|
+
proto="${raw%%://*}"
|
|
129
|
+
rest="${raw#*://}"
|
|
130
|
+
if [[ "$rest" != *"@"* ]] && [[ "$rest" == *:*:* ]]; then
|
|
131
|
+
local host port user pass
|
|
132
|
+
host=$(echo "$rest" | cut -d: -f1)
|
|
133
|
+
port=$(echo "$rest" | cut -d: -f2)
|
|
134
|
+
user=$(echo "$rest" | cut -d: -f3)
|
|
135
|
+
pass=$(echo "$rest" | cut -d: -f4-)
|
|
136
|
+
if [[ -n "$host" ]] && [[ -n "$port" ]] && [[ -n "$user" ]]; then
|
|
137
|
+
echo "${proto}://${user}:${pass}@${host}:${port}"
|
|
138
|
+
return
|
|
139
|
+
fi
|
|
140
|
+
fi
|
|
94
141
|
echo "$raw"
|
|
95
142
|
return
|
|
96
143
|
fi
|
|
@@ -107,9 +154,24 @@ _parse_proxy() {
|
|
|
107
154
|
fi
|
|
108
155
|
}
|
|
109
156
|
|
|
157
|
+
# curl-based health probes should use remote DNS for SOCKS5.
|
|
158
|
+
# Otherwise local DNS pollution can resolve probe domains to sinkhole/test
|
|
159
|
+
# addresses, causing false negatives even when the proxy itself is healthy.
|
|
160
|
+
_curl_proxy_url() {
|
|
161
|
+
local normalized
|
|
162
|
+
normalized=$(_parse_proxy "$1")
|
|
163
|
+
if [[ "$normalized" =~ ^socks5:// ]]; then
|
|
164
|
+
echo "socks5h://${normalized#socks5://}"
|
|
165
|
+
else
|
|
166
|
+
echo "$normalized"
|
|
167
|
+
fi
|
|
168
|
+
}
|
|
169
|
+
|
|
110
170
|
# socks5://user:pass@host:port → host:port
|
|
111
171
|
_proxy_host_port() {
|
|
112
|
-
|
|
172
|
+
local normalized
|
|
173
|
+
normalized=$(_parse_proxy "$1")
|
|
174
|
+
echo "$normalized" | sed 's|.*@||' | sed 's|.*://||'
|
|
113
175
|
}
|
|
114
176
|
|
|
115
177
|
_proxy_reachable() {
|
|
@@ -263,10 +325,20 @@ _envs_using_version() {
|
|
|
263
325
|
}
|
|
264
326
|
|
|
265
327
|
# Elapsed time helper: call _timer_start, then _timer_elapsed
|
|
266
|
-
|
|
328
|
+
_time_now() {
|
|
329
|
+
local now
|
|
330
|
+
now=$(date +%s%N 2>/dev/null || true)
|
|
331
|
+
if [[ "$now" =~ ^[0-9]{11,}$ ]]; then
|
|
332
|
+
echo "$now"
|
|
333
|
+
else
|
|
334
|
+
date +%s
|
|
335
|
+
fi
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
_timer_start() { _TIMER_START=$(_time_now); }
|
|
267
339
|
_timer_elapsed() {
|
|
268
|
-
local now; now=$(
|
|
269
|
-
if [[ $
|
|
340
|
+
local now; now=$(_time_now)
|
|
341
|
+
if [[ "$now" =~ ^[0-9]{11,}$ && "${_TIMER_START:-}" =~ ^[0-9]{11,}$ ]]; then
|
|
270
342
|
# nanoseconds available
|
|
271
343
|
local ms=$(( (now - _TIMER_START) / 1000000 ))
|
|
272
344
|
if [[ $ms -ge 1000 ]]; then
|
|
@@ -298,19 +370,31 @@ _detect_rc_file() {
|
|
|
298
370
|
local shell_name
|
|
299
371
|
shell_name=$(basename "${SHELL:-/bin/bash}")
|
|
300
372
|
case "$shell_name" in
|
|
373
|
+
fish)
|
|
374
|
+
echo "$HOME/.config/fish/config.fish"
|
|
375
|
+
return
|
|
376
|
+
;;
|
|
301
377
|
zsh)
|
|
302
|
-
|
|
378
|
+
echo "$HOME/.zshrc"
|
|
379
|
+
return
|
|
303
380
|
;;
|
|
304
381
|
bash)
|
|
305
382
|
[[ -f "$HOME/.bashrc" ]] && { echo "$HOME/.bashrc"; return; }
|
|
306
383
|
[[ -f "$HOME/.bash_profile" ]] && { echo "$HOME/.bash_profile"; return; }
|
|
384
|
+
echo "$HOME/.bashrc"
|
|
385
|
+
return
|
|
307
386
|
;;
|
|
308
387
|
esac
|
|
309
388
|
# Fallback: try common rc files
|
|
310
389
|
[[ -f "$HOME/.bashrc" ]] && { echo "$HOME/.bashrc"; return; }
|
|
311
390
|
[[ -f "$HOME/.zshrc" ]] && { echo "$HOME/.zshrc"; return; }
|
|
312
391
|
[[ -f "$HOME/.bash_profile" ]] && { echo "$HOME/.bash_profile"; return; }
|
|
313
|
-
echo ""
|
|
392
|
+
[[ -f "$HOME/.config/fish/config.fish" ]] && { echo "$HOME/.config/fish/config.fish"; return; }
|
|
393
|
+
echo "$HOME/.bashrc"
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
_is_fish_rc() {
|
|
397
|
+
[[ "$1" == */.config/fish/config.fish ]]
|
|
314
398
|
}
|
|
315
399
|
|
|
316
400
|
_install_method() {
|
|
@@ -339,16 +423,36 @@ _write_path_to_rc() {
|
|
|
339
423
|
return 0
|
|
340
424
|
fi
|
|
341
425
|
|
|
426
|
+
mkdir -p "$(dirname "$rc_file")"
|
|
427
|
+
[[ -f "$rc_file" ]] || touch "$rc_file"
|
|
428
|
+
|
|
342
429
|
if grep -q '# >>> cac >>>' "$rc_file" 2>/dev/null; then
|
|
343
430
|
echo " ✓ PATH already exists in $rc_file, skipping"
|
|
344
431
|
return 0
|
|
345
432
|
fi
|
|
346
433
|
|
|
347
434
|
# Compat: remove old format if present
|
|
348
|
-
if grep -q '\.cac/bin' "$rc_file" 2>/dev/null; then
|
|
435
|
+
if ! _is_fish_rc "$rc_file" && grep -q '\.cac/bin' "$rc_file" 2>/dev/null; then
|
|
349
436
|
_remove_path_from_rc "$rc_file"
|
|
350
437
|
fi
|
|
351
438
|
|
|
439
|
+
if _is_fish_rc "$rc_file"; then
|
|
440
|
+
cat >> "$rc_file" << 'CACEOF'
|
|
441
|
+
|
|
442
|
+
# >>> cac — Claude Code Cloak >>>
|
|
443
|
+
fish_add_path --move --path "$HOME/.cac/bin" >/dev/null 2>&1
|
|
444
|
+
function cac
|
|
445
|
+
command cac $argv
|
|
446
|
+
set -l _rc $status
|
|
447
|
+
fish_add_path --move --path "$HOME/.cac/bin" >/dev/null 2>&1
|
|
448
|
+
return $_rc
|
|
449
|
+
end
|
|
450
|
+
# <<< cac — Claude Code Cloak <<<
|
|
451
|
+
CACEOF
|
|
452
|
+
echo " ✓ PATH written to $rc_file"
|
|
453
|
+
return 0
|
|
454
|
+
fi
|
|
455
|
+
|
|
352
456
|
cat >> "$rc_file" << 'CACEOF'
|
|
353
457
|
|
|
354
458
|
# >>> cac — Claude Code Cloak >>>
|
|
@@ -1144,13 +1248,26 @@ fi
|
|
|
1144
1248
|
PROXY=""
|
|
1145
1249
|
if [[ -f "$_env_dir/proxy" ]]; then
|
|
1146
1250
|
PROXY=$(tr -d '[:space:]' < "$_env_dir/proxy")
|
|
1251
|
+
if [[ "$PROXY" =~ ^(http|https|socks5):// ]]; then
|
|
1252
|
+
_proto="${PROXY%%://*}"
|
|
1253
|
+
_rest="${PROXY#*://}"
|
|
1254
|
+
if [[ "$_rest" != *"@"* ]] && [[ "$_rest" == *:*:* ]]; then
|
|
1255
|
+
_phost=$(echo "$_rest" | cut -d: -f1)
|
|
1256
|
+
_pport=$(echo "$_rest" | cut -d: -f2)
|
|
1257
|
+
_puser=$(echo "$_rest" | cut -d: -f3)
|
|
1258
|
+
_ppass=$(echo "$_rest" | cut -d: -f4-)
|
|
1259
|
+
if [[ -n "$_phost" ]] && [[ -n "$_pport" ]] && [[ -n "$_puser" ]]; then
|
|
1260
|
+
PROXY="${_proto}://${_puser}:${_ppass}@${_phost}:${_pport}"
|
|
1261
|
+
fi
|
|
1262
|
+
fi
|
|
1263
|
+
fi
|
|
1147
1264
|
fi
|
|
1148
1265
|
|
|
1149
1266
|
if [[ -n "$PROXY" ]]; then
|
|
1150
1267
|
# pre-flight: proxy connectivity (pure bash, no fork)
|
|
1151
1268
|
_hp="${PROXY##*@}"; _hp="${_hp##*://}"
|
|
1152
1269
|
_host="${_hp%%:*}"
|
|
1153
|
-
_port
|
|
1270
|
+
_port=$(echo "$_hp" | cut -d: -f2)
|
|
1154
1271
|
if ! (echo >/dev/tcp/"$_host"/"$_port") 2>/dev/null; then
|
|
1155
1272
|
echo "[cac] error: [$_name] proxy $_hp unreachable, refusing to start." >&2
|
|
1156
1273
|
echo "[cac] hint: run 'cac check' to diagnose, or 'cac stop' to disable temporarily" >&2
|
|
@@ -1700,7 +1817,7 @@ _env_cmd_create() {
|
|
|
1700
1817
|
esac
|
|
1701
1818
|
done
|
|
1702
1819
|
|
|
1703
|
-
[[ -n "$name" ]] || _die "usage: cac env create <name> [-p <proxy>] [-c <version>] [--telemetry <mode>] [--persona <preset>]"
|
|
1820
|
+
[[ -n "$name" ]] || _die "usage: cac env create <name> [-p <proxy>] [-c <version>] [--clone [source]] [--no-link] [--telemetry <mode>] [--persona <preset>]"
|
|
1704
1821
|
[[ "$name" =~ ^[a-zA-Z0-9_-]+$ ]] || _die "invalid name '$name' (use alphanumeric, dash, underscore)"
|
|
1705
1822
|
|
|
1706
1823
|
local env_dir="$ENVS_DIR/$name"
|
|
@@ -1733,7 +1850,7 @@ _env_cmd_create() {
|
|
|
1733
1850
|
if [[ -n "$proxy_url" ]]; then
|
|
1734
1851
|
printf " $(_dim "Detecting timezone ...") "
|
|
1735
1852
|
local ip_info
|
|
1736
|
-
ip_info=$(curl -s --proxy "$proxy_url" --connect-timeout 8 "http://ip-api.com/json/?fields=timezone,countryCode" 2>/dev/null || true)
|
|
1853
|
+
ip_info=$(curl -s --proxy "$(_curl_proxy_url "$proxy_url")" --connect-timeout 8 "http://ip-api.com/json/?fields=timezone,countryCode" 2>/dev/null || true)
|
|
1737
1854
|
if [[ -n "$ip_info" ]]; then
|
|
1738
1855
|
local detected_tz country_code
|
|
1739
1856
|
read -r detected_tz country_code < <(echo "$ip_info" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('timezone',''), d.get('countryCode',''))" 2>/dev/null || echo "")
|
|
@@ -1891,7 +2008,7 @@ _env_cmd_ls() {
|
|
|
1891
2008
|
[[ -d "$env_dir" ]] || continue
|
|
1892
2009
|
names+=("$(basename "$env_dir")")
|
|
1893
2010
|
versions+=("$(_read "$env_dir/version" "system")")
|
|
1894
|
-
local p; p=$(_read "$env_dir/proxy" "")
|
|
2011
|
+
local p; p=$(_parse_proxy "$(_read "$env_dir/proxy" "")")
|
|
1895
2012
|
if [[ -n "$p" ]] && [[ "$p" == *"://"*"@"* ]]; then
|
|
1896
2013
|
p=$(echo "$p" | sed 's|://[^@]*@|://***@|')
|
|
1897
2014
|
fi
|
|
@@ -2098,8 +2215,9 @@ cmd_env() {
|
|
|
2098
2215
|
echo
|
|
2099
2216
|
echo " $(_bold "cac env") — environment management"
|
|
2100
2217
|
echo
|
|
2101
|
-
echo " $(_green "create") <name> [-p proxy] [-c ver] [--telemetry mode] [--persona preset]"
|
|
2218
|
+
echo " $(_green "create") <name> [-p proxy] [-c ver] [--clone [source]] [--no-link] [--telemetry mode] [--persona preset]"
|
|
2102
2219
|
echo " Create isolated environment (auto-activates)"
|
|
2220
|
+
echo " --clone inherits commands/hooks/skills/plugins; --no-link copies instead of symlink"
|
|
2103
2221
|
echo " $(_green "set") [name] <key> <value> Modify environment"
|
|
2104
2222
|
echo " proxy, version, telemetry, or persona"
|
|
2105
2223
|
echo " $(_green "ls") List all environments"
|
|
@@ -2126,7 +2244,7 @@ cmd_env() {
|
|
|
2126
2244
|
_relay_start() {
|
|
2127
2245
|
local name="${1:-$(_current_env)}"
|
|
2128
2246
|
local env_dir="$ENVS_DIR/$name"
|
|
2129
|
-
local proxy; proxy=$(_read "$env_dir/proxy")
|
|
2247
|
+
local proxy; proxy=$(_parse_proxy "$(_read "$env_dir/proxy")")
|
|
2130
2248
|
[[ -z "$proxy" ]] && return 1
|
|
2131
2249
|
|
|
2132
2250
|
local relay_js="$CAC_DIR/relay.js"
|
|
@@ -2289,7 +2407,7 @@ cmd_relay() {
|
|
|
2289
2407
|
|
|
2290
2408
|
# --route flag: add direct route
|
|
2291
2409
|
if [[ "$flag" == "--route" ]]; then
|
|
2292
|
-
local proxy; proxy=$(_read "$env_dir/proxy")
|
|
2410
|
+
local proxy; proxy=$(_parse_proxy "$(_read "$env_dir/proxy")")
|
|
2293
2411
|
_relay_add_route "$proxy"
|
|
2294
2412
|
fi
|
|
2295
2413
|
|
|
@@ -2359,7 +2477,7 @@ cmd_check() {
|
|
|
2359
2477
|
fi
|
|
2360
2478
|
|
|
2361
2479
|
local env_dir="$ENVS_DIR/$current"
|
|
2362
|
-
local proxy; proxy=$(_read "$env_dir/proxy" "")
|
|
2480
|
+
local proxy; proxy=$(_parse_proxy "$(_read "$env_dir/proxy" "")")
|
|
2363
2481
|
|
|
2364
2482
|
# Resolve version
|
|
2365
2483
|
local ver; ver=$(_read "$env_dir/version" "")
|
|
@@ -2545,7 +2663,7 @@ cmd_check() {
|
|
|
2545
2663
|
for _ip_url in $_urls; do
|
|
2546
2664
|
_dots="${_dots}."
|
|
2547
2665
|
printf "\r · exit IP $(_dim "detecting${_dots}")"
|
|
2548
|
-
proxy_ip=$(curl --proxy "$proxy" --connect-timeout 3 --max-time 6 "$_ip_url" 2>/dev/null || true)
|
|
2666
|
+
proxy_ip=$(curl --proxy "$(_curl_proxy_url "$proxy")" --connect-timeout 3 --max-time 6 "$_ip_url" 2>/dev/null || true)
|
|
2549
2667
|
[[ "$proxy_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && break
|
|
2550
2668
|
proxy_ip=""
|
|
2551
2669
|
done
|
|
@@ -2556,7 +2674,7 @@ cmd_check() {
|
|
|
2556
2674
|
local env_tz; env_tz=$(_read "$env_dir/tz" "")
|
|
2557
2675
|
if [[ -n "$env_tz" ]] && [[ -n "$proxy_ip" ]]; then
|
|
2558
2676
|
local ip_tz
|
|
2559
|
-
ip_tz=$(curl -s --proxy "$proxy" --connect-timeout 5 "http://ip-api.com/json/$proxy_ip?fields=timezone" 2>/dev/null | \
|
|
2677
|
+
ip_tz=$(curl -s --proxy "$(_curl_proxy_url "$proxy")" --connect-timeout 5 "http://ip-api.com/json/$proxy_ip?fields=timezone" 2>/dev/null | \
|
|
2560
2678
|
python3 -c "import sys,json; print(json.load(sys.stdin).get('timezone',''))" 2>/dev/null || true)
|
|
2561
2679
|
if [[ -n "$ip_tz" ]] && [[ "$ip_tz" != "$env_tz" ]]; then
|
|
2562
2680
|
echo " $(_yellow "⚠") TZ mismatch: env=$env_tz, IP=$ip_tz"
|
|
@@ -2564,7 +2682,8 @@ cmd_check() {
|
|
|
2564
2682
|
fi
|
|
2565
2683
|
fi
|
|
2566
2684
|
else
|
|
2567
|
-
printf "\r $(
|
|
2685
|
+
printf "\r $(_red "✗") exit IP $(_dim "unable to verify via proxy")\033[K\n"
|
|
2686
|
+
problems+=("exit IP undetected via proxy")
|
|
2568
2687
|
fi
|
|
2569
2688
|
|
|
2570
2689
|
# TUN conflict detection
|
|
@@ -3498,7 +3617,7 @@ cmd_help() {
|
|
|
3498
3617
|
echo
|
|
3499
3618
|
|
|
3500
3619
|
echo " $(_bold "Environment")"
|
|
3501
|
-
echo " $(_green "cac env create") <name> [-p proxy] [-c ver]"
|
|
3620
|
+
echo " $(_green "cac env create") <name> [-p proxy] [-c ver] [--clone [source]] [--no-link]"
|
|
3502
3621
|
echo " $(_green "cac env set") [name] <key> <value> Modify environment"
|
|
3503
3622
|
echo " $(_green "cac env ls") List all environments"
|
|
3504
3623
|
echo " $(_green "cac env rm") <name> Remove an environment"
|