claude-cac 1.5.5 → 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.
Files changed (2) hide show
  1. package/cac +100 -16
  2. 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.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 _model="${_models[$((RANDOM % ${#_models[@]}))]}"
74
- echo "${_name}s-${_model}.local"
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"; }
@@ -126,6 +154,19 @@ _parse_proxy() {
126
154
  fi
127
155
  }
128
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
+
129
170
  # socks5://user:pass@host:port → host:port
130
171
  _proxy_host_port() {
131
172
  local normalized
@@ -284,10 +325,20 @@ _envs_using_version() {
284
325
  }
285
326
 
286
327
  # Elapsed time helper: call _timer_start, then _timer_elapsed
287
- _timer_start() { _TIMER_START=$(date +%s%N 2>/dev/null || date +%s); }
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); }
288
339
  _timer_elapsed() {
289
- local now; now=$(date +%s%N 2>/dev/null || date +%s)
290
- if [[ ${#now} -gt 10 ]]; then
340
+ local now; now=$(_time_now)
341
+ if [[ "$now" =~ ^[0-9]{11,}$ && "${_TIMER_START:-}" =~ ^[0-9]{11,}$ ]]; then
291
342
  # nanoseconds available
292
343
  local ms=$(( (now - _TIMER_START) / 1000000 ))
293
344
  if [[ $ms -ge 1000 ]]; then
@@ -319,19 +370,31 @@ _detect_rc_file() {
319
370
  local shell_name
320
371
  shell_name=$(basename "${SHELL:-/bin/bash}")
321
372
  case "$shell_name" in
373
+ fish)
374
+ echo "$HOME/.config/fish/config.fish"
375
+ return
376
+ ;;
322
377
  zsh)
323
- [[ -f "$HOME/.zshrc" ]] && { echo "$HOME/.zshrc"; return; }
378
+ echo "$HOME/.zshrc"
379
+ return
324
380
  ;;
325
381
  bash)
326
382
  [[ -f "$HOME/.bashrc" ]] && { echo "$HOME/.bashrc"; return; }
327
383
  [[ -f "$HOME/.bash_profile" ]] && { echo "$HOME/.bash_profile"; return; }
384
+ echo "$HOME/.bashrc"
385
+ return
328
386
  ;;
329
387
  esac
330
388
  # Fallback: try common rc files
331
389
  [[ -f "$HOME/.bashrc" ]] && { echo "$HOME/.bashrc"; return; }
332
390
  [[ -f "$HOME/.zshrc" ]] && { echo "$HOME/.zshrc"; return; }
333
391
  [[ -f "$HOME/.bash_profile" ]] && { echo "$HOME/.bash_profile"; return; }
334
- 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 ]]
335
398
  }
336
399
 
337
400
  _install_method() {
@@ -360,16 +423,36 @@ _write_path_to_rc() {
360
423
  return 0
361
424
  fi
362
425
 
426
+ mkdir -p "$(dirname "$rc_file")"
427
+ [[ -f "$rc_file" ]] || touch "$rc_file"
428
+
363
429
  if grep -q '# >>> cac >>>' "$rc_file" 2>/dev/null; then
364
430
  echo " ✓ PATH already exists in $rc_file, skipping"
365
431
  return 0
366
432
  fi
367
433
 
368
434
  # Compat: remove old format if present
369
- 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
370
436
  _remove_path_from_rc "$rc_file"
371
437
  fi
372
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
+
373
456
  cat >> "$rc_file" << 'CACEOF'
374
457
 
375
458
  # >>> cac — Claude Code Cloak >>>
@@ -1734,7 +1817,7 @@ _env_cmd_create() {
1734
1817
  esac
1735
1818
  done
1736
1819
 
1737
- [[ -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>]"
1738
1821
  [[ "$name" =~ ^[a-zA-Z0-9_-]+$ ]] || _die "invalid name '$name' (use alphanumeric, dash, underscore)"
1739
1822
 
1740
1823
  local env_dir="$ENVS_DIR/$name"
@@ -1767,7 +1850,7 @@ _env_cmd_create() {
1767
1850
  if [[ -n "$proxy_url" ]]; then
1768
1851
  printf " $(_dim "Detecting timezone ...") "
1769
1852
  local ip_info
1770
- 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)
1771
1854
  if [[ -n "$ip_info" ]]; then
1772
1855
  local detected_tz country_code
1773
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 "")
@@ -2132,8 +2215,9 @@ cmd_env() {
2132
2215
  echo
2133
2216
  echo " $(_bold "cac env") — environment management"
2134
2217
  echo
2135
- 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]"
2136
2219
  echo " Create isolated environment (auto-activates)"
2220
+ echo " --clone inherits commands/hooks/skills/plugins; --no-link copies instead of symlink"
2137
2221
  echo " $(_green "set") [name] <key> <value> Modify environment"
2138
2222
  echo " proxy, version, telemetry, or persona"
2139
2223
  echo " $(_green "ls") List all environments"
@@ -2579,7 +2663,7 @@ cmd_check() {
2579
2663
  for _ip_url in $_urls; do
2580
2664
  _dots="${_dots}."
2581
2665
  printf "\r · exit IP $(_dim "detecting${_dots}")"
2582
- 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)
2583
2667
  [[ "$proxy_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && break
2584
2668
  proxy_ip=""
2585
2669
  done
@@ -2590,7 +2674,7 @@ cmd_check() {
2590
2674
  local env_tz; env_tz=$(_read "$env_dir/tz" "")
2591
2675
  if [[ -n "$env_tz" ]] && [[ -n "$proxy_ip" ]]; then
2592
2676
  local ip_tz
2593
- 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 | \
2594
2678
  python3 -c "import sys,json; print(json.load(sys.stdin).get('timezone',''))" 2>/dev/null || true)
2595
2679
  if [[ -n "$ip_tz" ]] && [[ "$ip_tz" != "$env_tz" ]]; then
2596
2680
  echo " $(_yellow "⚠") TZ mismatch: env=$env_tz, IP=$ip_tz"
@@ -3533,7 +3617,7 @@ cmd_help() {
3533
3617
  echo
3534
3618
 
3535
3619
  echo " $(_bold "Environment")"
3536
- 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]"
3537
3621
  echo " $(_green "cac env set") [name] <key> <value> Modify environment"
3538
3622
  echo " $(_green "cac env ls") List all environments"
3539
3623
  echo " $(_green "cac env rm") <name> Remove an environment"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cac",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "description": "Isolate, protect, and manage your Claude Code — versions, environments, identity, and proxy.",
5
5
  "bin": {
6
6
  "cac": "cac"