claude-cac 1.3.0 → 1.3.2

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 +191 -188
  2. package/package.json +1 -1
package/cac CHANGED
@@ -8,10 +8,10 @@ ENVS_DIR="$CAC_DIR/envs"
8
8
  VERSIONS_DIR="$CAC_DIR/versions"
9
9
 
10
10
  # ━━━ utils.sh ━━━
11
- # ── utils: 颜色、读写、UUIDproxy 解析 ───────────────────────
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.3.0"
14
+ CAC_VERSION="1.3.2"
15
15
 
16
16
  _read() { [[ -f "$1" ]] && tr -d '[:space:]' < "$1" || echo "${2:-}"; }
17
17
  _die() { printf '%b\n' "$(_red "error:") $*" >&2; exit 1; }
@@ -48,7 +48,7 @@ _new_machine_id() { _gen_uuid | tr -d '-' | tr '[:upper:]' '[:lower:]'; }
48
48
  _new_hostname() { echo "host-$(_gen_uuid | cut -d- -f1 | tr '[:upper:]' '[:lower:]')"; }
49
49
  _new_mac() { printf '02:%02x:%02x:%02x:%02x:%02x' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)); }
50
50
 
51
- # 获取真实命令路径(绕过 shim
51
+ # Get real command path (bypass shim)
52
52
  _get_real_cmd() {
53
53
  local cmd="$1"
54
54
  PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "$CAC_DIR/shim-bin" | tr '\n' ':') \
@@ -56,15 +56,15 @@ _get_real_cmd() {
56
56
  }
57
57
 
58
58
  # host:port:user:pass → http://user:pass@host:port
59
- # 或直接传入完整 URLhttp://、https://、socks5://)
59
+ # or pass a full URL directly (http://, https://, socks5://)
60
60
  _parse_proxy() {
61
61
  local raw="$1"
62
- # 如果已经是完整 URL,直接返回
62
+ # Already a full URL, return as-is
63
63
  if [[ "$raw" =~ ^(http|https|socks5):// ]]; then
64
64
  echo "$raw"
65
65
  return
66
66
  fi
67
- # 否则解析 host:port:user:pass 格式
67
+ # Parse host:port:user:pass format
68
68
  local host port user pass
69
69
  host=$(echo "$raw" | cut -d: -f1)
70
70
  port=$(echo "$raw" | cut -d: -f2)
@@ -90,11 +90,11 @@ _proxy_reachable() {
90
90
  (echo >/dev/tcp/"$host"/"$port") 2>/dev/null
91
91
  }
92
92
 
93
- # 自动检测代理协议(当用户未指定 http/socks5/https 时)
94
- # 用法:_auto_detect_proxy "host:port:user:pass" → 返回可用的完整 URL
93
+ # Auto-detect proxy protocol (when user didn't specify http/socks5/https)
94
+ # Usage: _auto_detect_proxy "host:port:user:pass" → returns a working full URL
95
95
  _auto_detect_proxy() {
96
96
  local raw="$1"
97
- # 已有协议前缀,直接返回
97
+ # Has protocol prefix, return as-is
98
98
  if [[ "$raw" =~ ^(http|https|socks5):// ]]; then
99
99
  echo "$raw"
100
100
  return 0
@@ -111,7 +111,7 @@ _auto_detect_proxy() {
111
111
  auth_part=""
112
112
  fi
113
113
 
114
- # 依次尝试 http → socks5 → https
114
+ # Try in order: http → socks5 → https
115
115
  local proto try_url
116
116
  for proto in http socks5 https; do
117
117
  try_url="${proto}://${auth_part}${host}:${port}"
@@ -121,7 +121,7 @@ _auto_detect_proxy() {
121
121
  fi
122
122
  done
123
123
 
124
- # 全部失败,回退 http
124
+ # All failed, fallback to http
125
125
  if [[ -n "$user" ]]; then
126
126
  echo "http://${auth_part}${host}:${port}"
127
127
  else
@@ -255,7 +255,7 @@ _require_setup() {
255
255
 
256
256
  _require_env() {
257
257
  [[ -d "$ENVS_DIR/$1" ]] || {
258
- echo "错误:环境 '$1' 不存在,用 'cac ls' 查看" >&2; exit 1
258
+ echo "error: environment '$1' not found, use 'cac ls' to list" >&2; exit 1
259
259
  }
260
260
  }
261
261
 
@@ -288,7 +288,7 @@ _install_method() {
288
288
  local resolved="$self"
289
289
  if [[ -L "$self" ]]; then
290
290
  resolved=$(readlink "$self" 2>/dev/null || echo "$self")
291
- # 处理相对路径的符号链接
291
+ # Handle relative symlinks
292
292
  if [[ "$resolved" != /* ]]; then
293
293
  resolved="$(dirname "$self")/$resolved"
294
294
  fi
@@ -303,18 +303,18 @@ _install_method() {
303
303
  _write_path_to_rc() {
304
304
  local rc_file="${1:-$(_detect_rc_file)}"
305
305
  if [[ -z "$rc_file" ]]; then
306
- echo " $(_yellow '⚠') 未找到 shell 配置文件,请手动添加 PATH"
306
+ echo " $(_yellow '⚠') shell config file not found, please add PATH manually:"
307
307
  echo ' export PATH="$HOME/bin:$PATH"'
308
308
  echo ' export PATH="$HOME/.cac/bin:$PATH"'
309
309
  return 0
310
310
  fi
311
311
 
312
312
  if grep -q '# >>> cac >>>' "$rc_file" 2>/dev/null; then
313
- echo " ✓ PATH 已存在于 $rc_file,跳过"
313
+ echo " ✓ PATH already exists in $rc_file, skipping"
314
314
  return 0
315
315
  fi
316
316
 
317
- # 兼容旧格式:如果存在旧的 cac PATH 行,先移除
317
+ # Compat: remove old format if present
318
318
  if grep -q '\.cac/bin' "$rc_file" 2>/dev/null; then
319
319
  _remove_path_from_rc "$rc_file"
320
320
  fi
@@ -322,18 +322,21 @@ _write_path_to_rc() {
322
322
  cat >> "$rc_file" << 'CACEOF'
323
323
 
324
324
  # >>> cac — Claude Code Cloak >>>
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"
325
+ PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '\.cac/bin' | tr '\n' ':' | sed 's/:$//')
326
+ export PATH="$HOME/.cac/bin:$PATH"
327
327
  cac() {
328
- command "$HOME/bin/cac" "$@"
328
+ local _cac_bin
329
+ _cac_bin=$(PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '\.cac/bin' | tr '\n' ':') command -v cac 2>/dev/null)
330
+ [[ -z "$_cac_bin" ]] && { echo "[cac] error: cac binary not found in PATH" >&2; return 1; }
331
+ command "$_cac_bin" "$@"
329
332
  local _rc=$?
330
333
  PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '\.cac/bin' | tr '\n' ':' | sed 's/:$//')
331
- export PATH="$HOME/.cac/bin:$HOME/bin:$PATH"
334
+ export PATH="$HOME/.cac/bin:$PATH"
332
335
  return $_rc
333
336
  }
334
337
  # <<< cac — Claude Code Cloak <<<
335
338
  CACEOF
336
- echo " ✓ PATH 已写入 $rc_file"
339
+ echo " ✓ PATH written to $rc_file"
337
340
  return 0
338
341
  }
339
342
 
@@ -341,23 +344,23 @@ _remove_path_from_rc() {
341
344
  local rc_file="${1:-$(_detect_rc_file)}"
342
345
  [[ -z "$rc_file" ]] && return 0
343
346
 
344
- # 移除标记块格式(新格式)
347
+ # Remove marked block (new format)
345
348
  if grep -q '# >>> cac' "$rc_file" 2>/dev/null; then
346
349
  local tmp="${rc_file}.cac-tmp"
347
350
  awk '/# >>> cac/{skip=1; next} /# <<< cac/{skip=0; next} !skip' "$rc_file" > "$tmp"
348
351
  cat -s "$tmp" > "$rc_file"
349
352
  rm -f "$tmp"
350
- echo " ✓ 已从 $rc_file 移除 PATH 配置"
353
+ echo " ✓ Removed PATH config from $rc_file"
351
354
  return 0
352
355
  fi
353
356
 
354
- # 兼容旧格式
357
+ # Compat: old format
355
358
  if grep -qE '(\.cac/bin|# cac —)' "$rc_file" 2>/dev/null; then
356
359
  local tmp="${rc_file}.cac-tmp"
357
360
  grep -vE '(# cac — Claude Code Cloak|\.cac/bin|# cac 命令|# claude wrapper)' "$rc_file" > "$tmp" || true
358
361
  cat -s "$tmp" > "$rc_file"
359
362
  rm -f "$tmp"
360
- echo " ✓ 已从 $rc_file 移除 PATH 配置(旧格式)"
363
+ echo " ✓ Removed PATH config from $rc_file (old format)"
361
364
  return 0
362
365
  fi
363
366
  }
@@ -400,9 +403,9 @@ PYEOF
400
403
  }
401
404
 
402
405
  # ━━━ dns_block.sh ━━━
403
- # ── DNS 拦截 & 遥测域名屏蔽 ─────────────────────────────────────
406
+ # ── DNS interception & telemetry domain blocking ─────────────────────────────────────
404
407
 
405
- # 需要拦截的遥测域名
408
+ # telemetry domains to block
406
409
  TELEMETRY_DOMAINS=(
407
410
  "statsig.anthropic.com"
408
411
  "sentry.io"
@@ -410,8 +413,8 @@ TELEMETRY_DOMAINS=(
410
413
  "cdn.growthbook.io"
411
414
  )
412
415
 
413
- # 写入 HOSTALIASES 文件(备用层:gethostbyname 级别拦截)
414
- # HOSTALIASES 格式为 hostname-to-hostname 映射(非 IP
416
+ # write HOSTALIASES file (fallback layer: gethostbyname-level blocking)
417
+ # HOSTALIASES format is hostname-to-hostname mapping (not IP)
415
418
  _write_blocked_hosts() {
416
419
  local hosts_file="$CAC_DIR/blocked_hosts"
417
420
  {
@@ -423,14 +426,14 @@ _write_blocked_hosts() {
423
426
  } > "$hosts_file"
424
427
  }
425
428
 
426
- # 写入 Node.js DNS guard 模块(核心层:dns.lookup / dns.resolve 级别拦截 + mTLS
429
+ # write Node.js DNS guard module (core layer: dns.lookup / dns.resolve level blocking + mTLS)
427
430
  _write_dns_guard_js() {
428
431
  local guard_file="$CAC_DIR/cac-dns-guard.js"
429
432
  cat > "$guard_file" << 'DNSGUARD_EOF'
430
433
  // ═══════════════════════════════════════════════════════════════
431
434
  // cac-dns-guard.js
432
- // NS 层级遥测域名拦截 mTLS 证书注入 fetch 防泄露
433
- // 通过 NODE_OPTIONS="--require <this>" 注入到 Claude Code 进程
435
+ // DNS-level telemetry domain blocking | mTLS certificate injection | fetch leak prevention
436
+ // Injected into Claude Code process via NODE_OPTIONS="--require <this>"
434
437
  // ═══════════════════════════════════════════════════════════════
435
438
  'use strict';
436
439
 
@@ -441,7 +444,7 @@ var http = require('http');
441
444
  var https = require('https');
442
445
  var fs = require('fs');
443
446
 
444
- // ─── 1. NS 层级遥测域名拦截 ──────────────────────────────────
447
+ // ─── 1. DNS-level telemetry domain blocking ──────────────────────────────────
445
448
 
446
449
  var BLOCKED_DOMAINS = new Set([
447
450
  'statsig.anthropic.com',
@@ -451,8 +454,8 @@ var BLOCKED_DOMAINS = new Set([
451
454
  ]);
452
455
 
453
456
  /**
454
- * 检查域名是否在拦截名单中(含子域匹配)
455
- * e.g. "foo.sentry.io" 会匹配 "sentry.io"
457
+ * Check if domain is in block list (including subdomain matching)
458
+ * e.g. "foo.sentry.io" matches "sentry.io"
456
459
  */
457
460
  function isDomainBlocked(hostname) {
458
461
  if (!hostname) return false;
@@ -475,7 +478,7 @@ function makeBlockedError(hostname, syscall) {
475
478
  return err;
476
479
  }
477
480
 
478
- // ── 1a. 拦截 dns.lookup ──
481
+ // ── 1a. intercept dns.lookup ──
479
482
  var _origLookup = dns.lookup;
480
483
  dns.lookup = function cacLookup(hostname, options, callback) {
481
484
  if (typeof options === 'function') { callback = options; options = {}; }
@@ -487,7 +490,7 @@ dns.lookup = function cacLookup(hostname, options, callback) {
487
490
  return _origLookup.call(dns, hostname, options, callback);
488
491
  };
489
492
 
490
- // ── 1b. 拦截 dns.resolve / resolve4 / resolve6 ──
493
+ // ── 1b. intercept dns.resolve / resolve4 / resolve6 ──
491
494
  ['resolve','resolve4','resolve6'].forEach(function(method) {
492
495
  var orig = dns[method];
493
496
  if (!orig) return;
@@ -503,7 +506,7 @@ dns.lookup = function cacLookup(hostname, options, callback) {
503
506
  };
504
507
  });
505
508
 
506
- // ── 1c. 拦截 dns.promises ──
509
+ // ── 1c. intercept dns.promises ──
507
510
  if (dns.promises) {
508
511
  var _origPLookup = dns.promises.lookup;
509
512
  if (_origPLookup) {
@@ -522,7 +525,7 @@ if (dns.promises) {
522
525
  });
523
526
  }
524
527
 
525
- // ── 1d. 网络层安全网:拦截 net.connect 到遥测域名 ──
528
+ // ── 1d. network layer safety net: intercept net.connect to telemetry domains ──
526
529
  var _origNetConnect = net.connect;
527
530
  var _origNetCreateConn = net.createConnection;
528
531
 
@@ -550,7 +553,7 @@ net.createConnection = function cacNetCreateConnection() {
550
553
  };
551
554
 
552
555
 
553
- // ─── 2. mTLS 客户端证书注入 ──────────────────────────────────
556
+ // ─── 2. mTLS certificate injection ──────────────────────────────────
554
557
 
555
558
  var mtlsCert = process.env.CAC_MTLS_CERT;
556
559
  var mtlsKey = process.env.CAC_MTLS_KEY;
@@ -568,14 +571,14 @@ if (mtlsCert && mtlsKey) {
568
571
  }
569
572
 
570
573
  if (certData && keyData) {
571
- // 仅对代理服务器连接注入 mTLS 证书
574
+ // inject mTLS cert only for proxy connections
572
575
  var proxyHost = proxyHostPort.split(':')[0];
573
576
  var proxyPort = parseInt(proxyHostPort.split(':')[1], 10) || 0;
574
577
 
575
- // 2a. 拦截 tls.connect,在代理连接时注入客户端证书
578
+ // 2a. intercept tls.connect, inject client cert for proxy connections
576
579
  var _origTlsConnect = tls.connect;
577
580
  tls.connect = function cacTlsConnect() {
578
- // 规范化参数:tls.connect(options[, cb]) tls.connect(port[, host][, options][, cb])
581
+ // normalize parameters: tls.connect(options[, cb]) or tls.connect(port[, host][, options][, cb])
579
582
  var args = Array.prototype.slice.call(arguments);
580
583
  var options, callback;
581
584
 
@@ -583,7 +586,7 @@ if (mtlsCert && mtlsKey) {
583
586
  options = args[0];
584
587
  callback = (typeof args[1] === 'function') ? args[1] : undefined;
585
588
  } else {
586
- // tls.connect(port, host, options, cb) 形式
589
+ // tls.connect(port, host, options, cb) form
587
590
  var port = args[0];
588
591
  var host = (typeof args[1] === 'string') ? args[1] : 'localhost';
589
592
  var optIdx = (typeof args[1] === 'string') ? 2 : 1;
@@ -594,7 +597,7 @@ if (mtlsCert && mtlsKey) {
594
597
  if (typeof callback !== 'function') callback = undefined;
595
598
  }
596
599
 
597
- // 仅在连接代理时注入(精确匹配 host:port
600
+ // inject only for proxy connections (exact host:port match)
598
601
  var targetHost = options.host || options.hostname || '';
599
602
  var targetPort = options.port || 0;
600
603
  if (proxyHost && targetHost === proxyHost &&
@@ -614,7 +617,7 @@ if (mtlsCert && mtlsKey) {
614
617
  return _origTlsConnect.call(tls, options);
615
618
  };
616
619
 
617
- // 2b. 注入 CA https.globalAgent(仅信任 CA,不注入客户端私钥)
620
+ // 2b. inject CA into https.globalAgent (trust CA only, no client private key)
618
621
  if (caData && https.globalAgent && https.globalAgent.options) {
619
622
  https.globalAgent.options.ca = https.globalAgent.options.ca
620
623
  ? [].concat(https.globalAgent.options.ca, caData)
@@ -624,15 +627,15 @@ if (mtlsCert && mtlsKey) {
624
627
  }
625
628
 
626
629
 
627
- // ─── 3. fetch 遥测拦截补丁 ──────────────────────────────────
628
- // Node.js 原生 fetch (undici) 不经过 dns.lookup,会绕过 DNS 拦截
629
- // 策略:优先用 node-fetch(走 http/https 模块 dns.lookup
630
- // 否则 wrap 原生 fetch 拦截遥测域名
630
+ // ─── 3. fetch telemetry interception patch ──────────────────────────────────
631
+ // Node.js native fetch (undici) bypasses dns.lookup, circumventing DNS blocking
632
+ // Strategy: prefer node-fetch (uses http/https modules -> dns.lookup)
633
+ // otherwise wrap native fetch to block telemetry domains
631
634
 
632
635
  (function patchFetch() {
633
636
  if (typeof globalThis === 'undefined') return;
634
637
 
635
- // 优先加载 node-fetch(基于 http/https 模块,天然走 dns.lookup 拦截链)
638
+ // prefer node-fetch (based on http/https modules, naturally goes through dns.lookup interception chain)
636
639
  try {
637
640
  var nodeFetch = require('node-fetch');
638
641
  if (nodeFetch && typeof nodeFetch === 'function') {
@@ -643,10 +646,10 @@ if (mtlsCert && mtlsKey) {
643
646
  return;
644
647
  }
645
648
  } catch(e) {
646
- // node-fetch 不可用
649
+ // node-fetch not available
647
650
  }
648
651
 
649
- // 兜底:包装原生 fetch,至少确保遥测域名被拦截
652
+ // fallback: wrap native fetch to ensure telemetry domains are blocked
650
653
  if (typeof globalThis.fetch === 'function') {
651
654
  var _origFetch = globalThis.fetch;
652
655
  globalThis.fetch = function cacFetch(input, init) {
@@ -666,15 +669,15 @@ if (mtlsCert && mtlsKey) {
666
669
  })();
667
670
 
668
671
 
669
- // ─── 4. 健康检查 bypass(进程内拦截,精确到 URL)────────────────
670
- // Claude Code 启动时 ping https://api.anthropic.com/api/hello
671
- // Cloudflare 拦截 Node.js TLS 指纹(JA3/JA4)→ 403
672
- // 方案:在 Node.js 层拦截该请求,直接返回 200,不发出任何网络流量
673
- // 仅拦截 health check,不影响 OAuth/API 等其他请求
672
+ // ─── 4. health check bypass (in-process interception, URL-specific) ────────────────
673
+ // Claude Code pings https://api.anthropic.com/api/hello on startup
674
+ // Cloudflare blocks Node.js TLS fingerprint (JA3/JA4) -> 403
675
+ // Solution: intercept this request at Node.js layer, return 200 directly, no network traffic
676
+ // Only intercepts health check, does not affect OAuth/API or other requests
674
677
 
675
678
  function isHealthCheck(url) {
676
679
  if (!url) return false;
677
- // 匹配 https://api.anthropic.com/api/hello 或带查询参数的变体
680
+ // match https://api.anthropic.com/api/hello or variants with query params
678
681
  return /^https?:\/\/api\.anthropic\.com\/api\/hello/.test(url);
679
682
  }
680
683
 
@@ -714,7 +717,7 @@ function makeHealthResponse(callback) {
714
717
  return req;
715
718
  }
716
719
 
717
- // 4a. 拦截 https.get / https.request
720
+ // 4a. intercept https.get / https.request
718
721
  var _origHttpsRequest = https.request;
719
722
  var _origHttpsGet = https.get;
720
723
 
@@ -752,7 +755,7 @@ https.get = function cacHttpsGet(urlOrOpts, optsOrCb, cb) {
752
755
  return _origHttpsGet.apply(https, arguments);
753
756
  };
754
757
 
755
- // 4b. 拦截 fetchundici 不走 https 模块)
758
+ // 4b. intercept fetch (undici bypasses https module)
756
759
  (function patchHealthFetch() {
757
760
  if (typeof globalThis === 'undefined' || typeof globalThis.fetch !== 'function') return;
758
761
  var _prevFetch = globalThis.fetch;
@@ -774,13 +777,13 @@ DNSGUARD_EOF
774
777
  chmod 644 "$guard_file"
775
778
  }
776
779
 
777
- # 验证 DNS 拦截是否生效
780
+ # verify DNS blocking is active
778
781
  _check_dns_block() {
779
782
  local domain="${1:-statsig.anthropic.com}"
780
783
  local guard_file="$CAC_DIR/cac-dns-guard.js"
781
784
 
782
785
  if [[ ! -f "$guard_file" ]]; then
783
- echo "$(_red "✗") DNS guard 模块不存在"
786
+ echo "$(_red "✗") DNS guard module not found"
784
787
  return 1
785
788
  fi
786
789
 
@@ -794,37 +797,37 @@ _check_dns_block() {
794
797
  ' "$guard_file" "$domain" 2>/dev/null || echo "ERROR")
795
798
 
796
799
  if [[ "$result" == "BLOCKED" ]]; then
797
- echo "$(_green "✓") $domain 已拦截"
800
+ echo "$(_green "✓") $domain blocked"
798
801
  return 0
799
802
  else
800
- echo "$(_red "✗") $domain 未被拦截 ($result)"
803
+ echo "$(_red "✗") $domain not blocked ($result)"
801
804
  return 1
802
805
  fi
803
806
  }
804
807
 
805
808
  # ━━━ mtls.sh ━━━
806
- # ── mTLS 客户端证书管理 ─────────────────────────────────────────
809
+ # ── mTLS client certificate management ─────────────────────────────────────────
807
810
 
808
- # 生成自签 CAsetup 时调用,仅生成一次)
811
+ # generate self-signed CA (called during setup, generated only once)
809
812
  _generate_ca_cert() {
810
813
  local ca_dir="$CAC_DIR/ca"
811
814
  local ca_key="$ca_dir/ca_key.pem"
812
815
  local ca_cert="$ca_dir/ca_cert.pem"
813
816
 
814
817
  if [[ -f "$ca_cert" ]] && [[ -f "$ca_key" ]]; then
815
- echo " CA 证书已存在,跳过生成"
818
+ echo " CA cert exists, skipping"
816
819
  return 0
817
820
  fi
818
821
 
819
822
  mkdir -p "$ca_dir"
820
823
 
821
- # 生成 CA 私钥(4096 RSA
824
+ # generate CA private key (4096-bit RSA)
822
825
  openssl genrsa -out "$ca_key" 4096 2>/dev/null || {
823
- echo "错误:生成 CA 私钥失败" >&2; return 1
826
+ echo "error: failed to generate CA private key" >&2; return 1
824
827
  }
825
828
  chmod 600 "$ca_key"
826
829
 
827
- # 生成自签 CA 证书(有效期 10 年)
830
+ # generate self-signed CA cert (valid for 10 years)
828
831
  openssl req -new -x509 \
829
832
  -key "$ca_key" \
830
833
  -out "$ca_cert" \
@@ -833,12 +836,12 @@ _generate_ca_cert() {
833
836
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
834
837
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
835
838
  2>/dev/null || {
836
- echo "错误:生成 CA 证书失败" >&2; return 1
839
+ echo "error: failed to generate CA cert" >&2; return 1
837
840
  }
838
841
  chmod 644 "$ca_cert"
839
842
  }
840
843
 
841
- # 为指定环境生成客户端证书(cac add 时调用)
844
+ # generate client cert for environment (called during cac add)
842
845
  _generate_client_cert() {
843
846
  local name="$1"
844
847
  local env_dir="$ENVS_DIR/$name"
@@ -846,7 +849,7 @@ _generate_client_cert() {
846
849
  local ca_cert="$CAC_DIR/ca/ca_cert.pem"
847
850
 
848
851
  if [[ ! -f "$ca_key" ]] || [[ ! -f "$ca_cert" ]]; then
849
- echo " 警告:CA 证书不存在,跳过客户端证书生成" >&2
852
+ echo " warning: CA cert not found, skipping client cert generation" >&2
850
853
  return 1
851
854
  fi
852
855
 
@@ -854,22 +857,22 @@ _generate_client_cert() {
854
857
  local client_csr="$env_dir/client_csr.pem"
855
858
  local client_cert="$env_dir/client_cert.pem"
856
859
 
857
- # 生成客户端私钥(2048 RSA
860
+ # generate client private key (2048-bit RSA)
858
861
  openssl genrsa -out "$client_key" 2048 2>/dev/null || {
859
- echo "错误:生成客户端私钥失败" >&2; return 1
862
+ echo "error: failed to generate client private key" >&2; return 1
860
863
  }
861
864
  chmod 600 "$client_key"
862
865
 
863
- # 生成 CSR
866
+ # generate CSR
864
867
  openssl req -new \
865
868
  -key "$client_key" \
866
869
  -out "$client_csr" \
867
870
  -subj "/CN=cac-client-${name}/O=cac/OU=env-${name}" \
868
871
  2>/dev/null || {
869
- echo "错误:生成 CSR 失败" >&2; return 1
872
+ echo "error: failed to generate CSR" >&2; return 1
870
873
  }
871
874
 
872
- # CA 签发客户端证书(有效期 1 年)
875
+ # sign client cert with CA (valid for 1 year)
873
876
  openssl x509 -req \
874
877
  -in "$client_csr" \
875
878
  -CA "$ca_cert" \
@@ -879,52 +882,52 @@ _generate_client_cert() {
879
882
  -days 365 \
880
883
  -extfile <(printf "keyUsage=critical,digitalSignature\nextendedKeyUsage=clientAuth") \
881
884
  2>/dev/null || {
882
- echo "错误:签发客户端证书失败" >&2; return 1
885
+ echo "error: failed to sign client cert" >&2; return 1
883
886
  }
884
887
  chmod 644 "$client_cert"
885
888
 
886
- # 清理 CSR(不再需要)
889
+ # cleanup CSR (no longer needed)
887
890
  rm -f "$client_csr"
888
891
  }
889
892
 
890
- # 验证 mTLS 证书状态
893
+ # verify mTLS certificate status
891
894
  _check_mtls() {
892
895
  local env_dir="$1"
893
896
  local ca_cert="$CAC_DIR/ca/ca_cert.pem"
894
897
  local client_cert="$env_dir/client_cert.pem"
895
898
  local client_key="$env_dir/client_key.pem"
896
899
 
897
- # 检查 CA
900
+ # check CA
898
901
  if [[ ! -f "$ca_cert" ]]; then
899
- echo "$(_red "✗") CA 证书不存在"
902
+ echo "$(_red "✗") CA cert not found"
900
903
  return 1
901
904
  fi
902
905
 
903
- # 检查客户端证书
906
+ # check client cert
904
907
  if [[ ! -f "$client_cert" ]] || [[ ! -f "$client_key" ]]; then
905
- echo "$(_yellow "⚠") 客户端证书不存在"
908
+ echo "$(_yellow "⚠") client cert not found"
906
909
  return 1
907
910
  fi
908
911
 
909
- # 验证证书链
912
+ # verify certificate chain
910
913
  if openssl verify -CAfile "$ca_cert" "$client_cert" >/dev/null 2>&1; then
911
- # 检查证书有效期
914
+ # check certificate expiry
912
915
  local expiry
913
916
  expiry=$(openssl x509 -in "$client_cert" -noout -enddate 2>/dev/null | cut -d= -f2)
914
917
  local cn
915
918
  cn=$(openssl x509 -in "$client_cert" -noout -subject 2>/dev/null | sed 's/.*CN *= *//')
916
- echo "$(_green "✓") mTLS 证书有效 (CN=$cn, 到期: $expiry)"
919
+ echo "$(_green "✓") mTLS certificate valid (CN=$cn, expires: $expiry)"
917
920
  return 0
918
921
  else
919
- echo "$(_red "✗") 证书链验证失败"
922
+ echo "$(_red "✗") certificate chain verification failed"
920
923
  return 1
921
924
  fi
922
925
  }
923
926
 
924
927
  # ━━━ templates.sh ━━━
925
- # ── templates: 写入 wrapper、shim、环境初始化 ──────────────────
928
+ # ── templates: wrapper, shim, env init ──────────────────
926
929
 
927
- # 写入 statusline-command.sh 到环境 .claude 目录
930
+ # write statusline-command.sh to env .claude dir
928
931
  _write_statusline_script() {
929
932
  local config_dir="$1"
930
933
  cat > "$config_dir/statusline-command.sh" << 'STATUSLINE_EOF'
@@ -1017,7 +1020,7 @@ STATUSLINE_EOF
1017
1020
  chmod +x "$config_dir/statusline-command.sh"
1018
1021
  }
1019
1022
 
1020
- # 写入 settings.json 到环境 .claude 目录
1023
+ # write settings.json to env .claude dir
1021
1024
  _write_env_settings() {
1022
1025
  local config_dir="$1"
1023
1026
  cat > "$config_dir/settings.json" << 'SETTINGS_EOF'
@@ -1034,7 +1037,7 @@ _write_env_settings() {
1034
1037
  SETTINGS_EOF
1035
1038
  }
1036
1039
 
1037
- # 写入 CLAUDE.md 到环境 .claude 目录
1040
+ # write CLAUDE.md to env .claude dir
1038
1041
  _write_env_claude_md() {
1039
1042
  local config_dir="$1"
1040
1043
  local env_name="$2"
@@ -1063,25 +1066,25 @@ set -euo pipefail
1063
1066
  CAC_DIR="$HOME/.cac"
1064
1067
  ENVS_DIR="$CAC_DIR/envs"
1065
1068
 
1066
- # cacstop 状态:直接透传
1069
+ # cacstop state: passthrough directly
1067
1070
  if [[ -f "$CAC_DIR/stopped" ]]; then
1068
1071
  _real=$(tr -d '[:space:]' < "$CAC_DIR/real_claude" 2>/dev/null || true)
1069
1072
  [[ -x "$_real" ]] && exec "$_real" "$@"
1070
- echo "[cac] 错误:找不到真实 claude,运行 'cac setup'" >&2; exit 1
1073
+ echo "[cac] error: real claude not found, run 'cac setup'" >&2; exit 1
1071
1074
  fi
1072
1075
 
1073
- # 读取当前环境
1076
+ # read current environment
1074
1077
  if [[ ! -f "$CAC_DIR/current" ]]; then
1075
- echo "[cac] 错误:未激活任何环境,运行 'cac <name>'" >&2; exit 1
1078
+ echo "[cac] error: no active environment, run 'cac <name>'" >&2; exit 1
1076
1079
  fi
1077
1080
  _name=$(tr -d '[:space:]' < "$CAC_DIR/current")
1078
1081
  _env_dir="$ENVS_DIR/$_name"
1079
- [[ -d "$_env_dir" ]] || { echo "[cac] 错误:环境 '$_name' 不存在" >&2; exit 1; }
1082
+ [[ -d "$_env_dir" ]] || { echo "[cac] error: environment '$_name' not found" >&2; exit 1; }
1080
1083
 
1081
1084
  # Isolated .claude config directory
1082
1085
  if [[ -d "$_env_dir/.claude" ]]; then
1083
1086
  export CLAUDE_CONFIG_DIR="$_env_dir/.claude"
1084
- # 确保 settings.json 存在,阻止 Claude Code fallback ~/.claude/settings.json
1087
+ # ensure settings.json exists, prevent Claude Code fallback to ~/.claude/settings.json
1085
1088
  [[ -f "$_env_dir/.claude/settings.json" ]] || echo '{}' > "$_env_dir/.claude/settings.json"
1086
1089
  fi
1087
1090
 
@@ -1092,18 +1095,18 @@ if [[ -f "$_env_dir/proxy" ]]; then
1092
1095
  fi
1093
1096
 
1094
1097
  if [[ -n "$PROXY" ]]; then
1095
- # pre-flight:代理连通性(纯 bash,无 fork
1098
+ # pre-flight: proxy connectivity (pure bash, no fork)
1096
1099
  _hp="${PROXY##*@}"; _hp="${_hp##*://}"
1097
1100
  _host="${_hp%%:*}"
1098
1101
  _port="${_hp##*:}"
1099
1102
  if ! (echo >/dev/tcp/"$_host"/"$_port") 2>/dev/null; then
1100
- echo "[cac] 错误:[$_name] 代理 $_hp 不通,拒绝启动。" >&2
1101
- echo "[cac] 提示:运行 'cac check' 排查,或 'cac stop' 临时停用" >&2
1103
+ echo "[cac] error: [$_name] proxy $_hp unreachable, refusing to start." >&2
1104
+ echo "[cac] hint: run 'cac check' to diagnose, or 'cac stop' to disable temporarily" >&2
1102
1105
  exit 1
1103
1106
  fi
1104
1107
  fi
1105
1108
 
1106
- # 注入 statsig stable_id
1109
+ # inject statsig stable_id
1107
1110
  if [[ -f "$_env_dir/stable_id" ]]; then
1108
1111
  _sid=$(tr -d '[:space:]' < "$_env_dir/stable_id")
1109
1112
  _config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
@@ -1116,7 +1119,7 @@ if [[ -f "$_env_dir/stable_id" ]]; then
1116
1119
  fi
1117
1120
  fi
1118
1121
 
1119
- # 注入环境变量 —— 代理(仅在配置了代理时)
1122
+ # inject env vars — proxy (only when proxy is configured)
1120
1123
  if [[ -n "$PROXY" ]]; then
1121
1124
  export _CAC_PROXY="$PROXY"
1122
1125
  export HTTPS_PROXY="$PROXY" HTTP_PROXY="$PROXY" ALL_PROXY="$PROXY"
@@ -1124,45 +1127,45 @@ if [[ -n "$PROXY" ]]; then
1124
1127
  fi
1125
1128
  export PATH="$CAC_DIR/shim-bin:$PATH"
1126
1129
 
1127
- # ── 多层环境变量遥测保护 ──
1128
- # Layer 1: Claude Code 原生开关
1130
+ # ── multi-layer telemetry protection ──
1131
+ # Layer 1: Claude Code native toggle
1129
1132
  export CLAUDE_CODE_ENABLE_TELEMETRY=
1130
- # Layer 2: 通用遥测标准 (https://consoledonottrack.com)
1133
+ # Layer 2: universal telemetry opt-out (https://consoledonottrack.com)
1131
1134
  export DO_NOT_TRACK=1
1132
- # Layer 3: OpenTelemetry SDK 全面禁用
1135
+ # Layer 3: OpenTelemetry SDK fully disabled
1133
1136
  export OTEL_SDK_DISABLED=true
1134
1137
  export OTEL_TRACES_EXPORTER=none
1135
1138
  export OTEL_METRICS_EXPORTER=none
1136
1139
  export OTEL_LOGS_EXPORTER=none
1137
- # Layer 4: Sentry DSN 置空,阻止错误上报
1140
+ # Layer 4: empty Sentry DSN, block error reporting
1138
1141
  export SENTRY_DSN=
1139
- # Layer 5: Claude Code 特有开关
1142
+ # Layer 5: Claude Code specific toggles
1140
1143
  export DISABLE_ERROR_REPORTING=1
1141
1144
  export DISABLE_BUG_COMMAND=1
1142
1145
  export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
1143
- # Layer 6: 其他已知遥测标志
1146
+ # Layer 6: other known telemetry flags
1144
1147
  export TELEMETRY_DISABLED=1
1145
1148
  export DISABLE_TELEMETRY=1
1146
1149
 
1147
- # 有代理时:强制走 OAuth(清除 API 配置防泄露)
1148
- # 无代理时:保留用户的 API Key / Base URL
1150
+ # with proxy: force OAuth (clear API config to prevent leaks)
1151
+ # without proxy: preserve user's API Key / Base URL
1149
1152
  if [[ -n "$PROXY" ]]; then
1150
1153
  unset ANTHROPIC_BASE_URL
1151
1154
  unset ANTHROPIC_AUTH_TOKEN
1152
1155
  unset ANTHROPIC_API_KEY
1153
1156
  fi
1154
1157
 
1155
- # ── NS 层级 DNS 拦截 ──
1158
+ # ── NS-level DNS interception ──
1156
1159
  if [[ -f "$CAC_DIR/cac-dns-guard.js" ]]; then
1157
1160
  case "${NODE_OPTIONS:-}" in
1158
- *cac-dns-guard.js*) ;; # 已注入,跳过
1161
+ *cac-dns-guard.js*) ;; # already injected, skip
1159
1162
  *) export NODE_OPTIONS="${NODE_OPTIONS:-} --require $CAC_DIR/cac-dns-guard.js" ;;
1160
1163
  esac
1161
1164
  fi
1162
- # 备用层:HOSTALIASESgethostbyname 级别)
1165
+ # fallback layer: HOSTALIASES (gethostbyname level)
1163
1166
  [[ -f "$CAC_DIR/blocked_hosts" ]] && export HOSTALIASES="$CAC_DIR/blocked_hosts"
1164
1167
 
1165
- # ── mTLS 客户端证书 ──
1168
+ # ── mTLS client certificate ──
1166
1169
  if [[ -f "$_env_dir/client_cert.pem" ]] && [[ -f "$_env_dir/client_key.pem" ]]; then
1167
1170
  export CAC_MTLS_CERT="$_env_dir/client_cert.pem"
1168
1171
  export CAC_MTLS_KEY="$_env_dir/client_key.pem"
@@ -1173,7 +1176,7 @@ if [[ -f "$_env_dir/client_cert.pem" ]] && [[ -f "$_env_dir/client_key.pem" ]];
1173
1176
  [[ -n "${_hp:-}" ]] && export CAC_PROXY_HOST="$_hp"
1174
1177
  fi
1175
1178
 
1176
- # 确保 CA 证书始终被信任(mTLS 需要)
1179
+ # ensure CA cert is always trusted (required for mTLS)
1177
1180
  [[ -f "$CAC_DIR/ca/ca_cert.pem" ]] && export NODE_EXTRA_CA_CERTS="$CAC_DIR/ca/ca_cert.pem"
1178
1181
 
1179
1182
  [[ -f "$_env_dir/tz" ]] && export TZ=$(tr -d '[:space:]' < "$_env_dir/tz")
@@ -1183,18 +1186,18 @@ if [[ -f "$_env_dir/hostname" ]]; then
1183
1186
  export HOSTNAME="$_hn" CAC_HOSTNAME="$_hn"
1184
1187
  fi
1185
1188
 
1186
- # Node.js 级指纹拦截(绕过 shell shim 限制)
1189
+ # Node.js-level fingerprint interception (bypasses shell shim limitations)
1187
1190
  [[ -f "$_env_dir/mac_address" ]] && export CAC_MAC=$(tr -d '[:space:]' < "$_env_dir/mac_address")
1188
1191
  [[ -f "$_env_dir/machine_id" ]] && export CAC_MACHINE_ID=$(tr -d '[:space:]' < "$_env_dir/machine_id")
1189
1192
  export CAC_USERNAME="user-$(echo "$_name" | cut -c1-8)"
1190
1193
  if [[ -f "$CAC_DIR/fingerprint-hook.js" ]]; then
1191
1194
  case "${NODE_OPTIONS:-}" in
1192
- *fingerprint-hook.js*) ;; # 已注入,跳过
1195
+ *fingerprint-hook.js*) ;; # already injected, skip
1193
1196
  *) export NODE_OPTIONS="--require $CAC_DIR/fingerprint-hook.js ${NODE_OPTIONS:-}" ;;
1194
1197
  esac
1195
1198
  fi
1196
1199
 
1197
- # 执行真实 claude — versioned binary or system fallback
1200
+ # exec real claude — versioned binary or system fallback
1198
1201
  _real=""
1199
1202
  if [[ -f "$_env_dir/version" ]]; then
1200
1203
  _ver=$(tr -d '[:space:]' < "$_env_dir/version")
@@ -1204,23 +1207,23 @@ fi
1204
1207
  if [[ -z "$_real" ]] || [[ ! -x "$_real" ]]; then
1205
1208
  _real=$(tr -d '[:space:]' < "$CAC_DIR/real_claude")
1206
1209
  fi
1207
- [[ -x "$_real" ]] || { echo "[cac] 错误:找不到 claude,运行 'cac setup'" >&2; exit 1; }
1210
+ [[ -x "$_real" ]] || { echo "[cac] error: claude not found, run 'cac setup'" >&2; exit 1; }
1208
1211
 
1209
- # ── Relay 本地中转(有代理时始终启用)──
1212
+ # ── Relay local forwarding (always enabled when proxy is set) ──
1210
1213
  _relay_active=false
1211
1214
  if [[ -n "$PROXY" ]] && [[ -f "$CAC_DIR/relay.js" ]]; then
1212
1215
  _relay_js="$CAC_DIR/relay.js"
1213
1216
  _relay_pid_file="$CAC_DIR/relay.pid"
1214
1217
  _relay_port_file="$CAC_DIR/relay.port"
1215
1218
 
1216
- # 检查 relay 是否已在运行
1219
+ # check if relay is already running
1217
1220
  _relay_running=false
1218
1221
  if [[ -f "$_relay_pid_file" ]]; then
1219
1222
  _rpid=$(tr -d '[:space:]' < "$_relay_pid_file")
1220
1223
  kill -0 "$_rpid" 2>/dev/null && _relay_running=true
1221
1224
  fi
1222
1225
 
1223
- # 未运行则启动
1226
+ # start if not running
1224
1227
  if [[ "$_relay_running" != "true" ]] && [[ -f "$_relay_js" ]]; then
1225
1228
  _rport=17890
1226
1229
  while (echo >/dev/tcp/127.0.0.1/$_rport) 2>/dev/null; do
@@ -1236,7 +1239,7 @@ if [[ -n "$PROXY" ]] && [[ -f "$CAC_DIR/relay.js" ]]; then
1236
1239
  echo "$_rport" > "$_relay_port_file"
1237
1240
  fi
1238
1241
 
1239
- # 覆盖代理指向本地 relay
1242
+ # override proxy to point to local relay
1240
1243
  if [[ -f "$_relay_port_file" ]]; then
1241
1244
  _rport=$(tr -d '[:space:]' < "$_relay_port_file")
1242
1245
  export HTTPS_PROXY="http://127.0.0.1:$_rport"
@@ -1246,9 +1249,9 @@ if [[ -n "$PROXY" ]] && [[ -f "$CAC_DIR/relay.js" ]]; then
1246
1249
  fi
1247
1250
  fi
1248
1251
 
1249
- # 清理函数
1252
+ # cleanup function
1250
1253
  _cleanup_all() {
1251
- # 清理 relay
1254
+ # cleanup relay
1252
1255
  if [[ "$_relay_active" == "true" ]] && [[ -f "$CAC_DIR/relay.pid" ]]; then
1253
1256
  local _p; _p=$(cat "$CAC_DIR/relay.pid" 2>/dev/null) || true
1254
1257
  [[ -n "$_p" ]] && kill "$_p" 2>/dev/null || true
@@ -1271,7 +1274,7 @@ _write_ioreg_shim() {
1271
1274
  #!/usr/bin/env bash
1272
1275
  CAC_DIR="$HOME/.cac"
1273
1276
 
1274
- # 非目标调用:透传真实 ioreg
1277
+ # non-target call: passthrough to real ioreg
1275
1278
  if ! echo "$*" | grep -q "IOPlatformExpertDevice"; then
1276
1279
  _real=$(PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "$CAC_DIR/shim-bin" | tr '\n' ':') \
1277
1280
  command -v ioreg 2>/dev/null || true)
@@ -1279,7 +1282,7 @@ if ! echo "$*" | grep -q "IOPlatformExpertDevice"; then
1279
1282
  exit 0
1280
1283
  fi
1281
1284
 
1282
- # 读取当前环境的 UUID
1285
+ # read current env UUID
1283
1286
  _uuid_file="$CAC_DIR/envs/$(tr -d '[:space:]' < "$CAC_DIR/current" 2>/dev/null)/uuid"
1284
1287
  if [[ ! -f "$_uuid_file" ]]; then
1285
1288
  _real=$(PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "$CAC_DIR/shim-bin" | tr '\n' ':') \
@@ -1309,10 +1312,10 @@ _write_machine_id_shim() {
1309
1312
  #!/usr/bin/env bash
1310
1313
  CAC_DIR="$HOME/.cac"
1311
1314
 
1312
- # 先获取真实 cat 路径(避免递归调用自身)
1315
+ # get real cat path first (avoid recursive self-call)
1313
1316
  _real=$(PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "$CAC_DIR/shim-bin" | tr '\n' ':') command -v cat 2>/dev/null || true)
1314
1317
 
1315
- # 拦截 /etc/machine-id /var/lib/dbus/machine-id
1318
+ # intercept /etc/machine-id and /var/lib/dbus/machine-id
1316
1319
  if [[ "$1" == "/etc/machine-id" ]] || [[ "$1" == "/var/lib/dbus/machine-id" ]]; then
1317
1320
  _mid_file="$CAC_DIR/envs/$(tr -d '[:space:]' < "$CAC_DIR/current" 2>/dev/null)/machine_id"
1318
1321
  if [[ -f "$_mid_file" ]] && [[ -n "$_real" ]]; then
@@ -1320,7 +1323,7 @@ if [[ "$1" == "/etc/machine-id" ]] || [[ "$1" == "/var/lib/dbus/machine-id" ]];
1320
1323
  fi
1321
1324
  fi
1322
1325
 
1323
- # 非目标调用:透传真实 cat
1326
+ # non-target call: passthrough to real cat
1324
1327
  [[ -n "$_real" ]] && exec "$_real" "$@"
1325
1328
  exit 1
1326
1329
  CAT_EOF
@@ -1333,14 +1336,14 @@ _write_hostname_shim() {
1333
1336
  #!/usr/bin/env bash
1334
1337
  CAC_DIR="$HOME/.cac"
1335
1338
 
1336
- # 读取伪造的 hostname
1339
+ # read spoofed hostname
1337
1340
  _hn_file="$CAC_DIR/envs/$(tr -d '[:space:]' < "$CAC_DIR/current" 2>/dev/null)/hostname"
1338
1341
  if [[ -f "$_hn_file" ]]; then
1339
1342
  tr -d '[:space:]' < "$_hn_file"
1340
1343
  exit 0
1341
1344
  fi
1342
1345
 
1343
- # 透传真实 hostname
1346
+ # passthrough to real hostname
1344
1347
  _real=$(PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "$CAC_DIR/shim-bin" | tr '\n' ':') command -v hostname 2>/dev/null || true)
1345
1348
  [[ -n "$_real" ]] && exec "$_real" "$@"
1346
1349
  exit 1
@@ -1785,7 +1788,7 @@ cmd_env() {
1785
1788
  }
1786
1789
 
1787
1790
  # ━━━ cmd_relay.sh ━━━
1788
- # ── cmd: relay(本地中转,绕过 TUN)──────────────────────────────
1791
+ # ── cmd: relay (local relay, bypass TUN) ──────────────────────────────
1789
1792
 
1790
1793
  _relay_start() {
1791
1794
  local name="${1:-$(_current_env)}"
@@ -1794,14 +1797,14 @@ _relay_start() {
1794
1797
  [[ -z "$proxy" ]] && return 1
1795
1798
 
1796
1799
  local relay_js="$CAC_DIR/relay.js"
1797
- [[ -f "$relay_js" ]] || { echo "错误:relay.js 未找到,请运行 'cac setup'" >&2; return 1; }
1800
+ [[ -f "$relay_js" ]] || { echo "error: relay.js not found, run 'cac setup'" >&2; return 1; }
1798
1801
 
1799
- # 寻找可用端口(17890-17999
1802
+ # find available port (17890-17999)
1800
1803
  local port=17890
1801
1804
  while (echo >/dev/tcp/127.0.0.1/$port) 2>/dev/null; do
1802
1805
  (( port++ ))
1803
1806
  if [[ $port -gt 17999 ]]; then
1804
- echo "错误:端口 17890-17999 全部被占用" >&2
1807
+ echo "error: all ports 17890-17999 occupied" >&2
1805
1808
  return 1
1806
1809
  fi
1807
1810
  done
@@ -1810,7 +1813,7 @@ _relay_start() {
1810
1813
  node "$relay_js" "$port" "$proxy" "$pid_file" </dev/null >"$CAC_DIR/relay.log" 2>&1 &
1811
1814
  disown
1812
1815
 
1813
- # 等待 relay 就绪
1816
+ # wait for relay ready
1814
1817
  local _i
1815
1818
  for _i in {1..30}; do
1816
1819
  (echo >/dev/tcp/127.0.0.1/$port) 2>/dev/null && break
@@ -1818,7 +1821,7 @@ _relay_start() {
1818
1821
  done
1819
1822
 
1820
1823
  if ! (echo >/dev/tcp/127.0.0.1/$port) 2>/dev/null; then
1821
- echo "错误:relay 启动超时" >&2
1824
+ echo "error: relay startup timeout" >&2
1822
1825
  return 1
1823
1826
  fi
1824
1827
 
@@ -1832,7 +1835,7 @@ _relay_stop() {
1832
1835
  local pid; pid=$(tr -d '[:space:]' < "$pid_file")
1833
1836
  if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
1834
1837
  kill "$pid" 2>/dev/null
1835
- # 等待进程退出
1838
+ # wait for process exit
1836
1839
  local _i
1837
1840
  for _i in {1..20}; do
1838
1841
  kill -0 "$pid" 2>/dev/null || break
@@ -1843,7 +1846,7 @@ _relay_stop() {
1843
1846
  fi
1844
1847
  rm -f "$CAC_DIR/relay.port"
1845
1848
 
1846
- # 清理路由
1849
+ # cleanup route
1847
1850
  _relay_remove_route 2>/dev/null || true
1848
1851
  }
1849
1852
 
@@ -1854,17 +1857,17 @@ _relay_is_running() {
1854
1857
  [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null
1855
1858
  }
1856
1859
 
1857
- # ── 路由管理(绕过 TUN 的直连路由)──────────────────────────────
1860
+ # ── route management (direct route to bypass TUN) ──────────────────────────────
1858
1861
 
1859
1862
  _relay_add_route() {
1860
1863
  local proxy="$1"
1861
1864
  local proxy_host; proxy_host=$(_proxy_host_port "$proxy")
1862
1865
  proxy_host="${proxy_host%%:*}"
1863
1866
 
1864
- # 跳过已是 IP 的回环地址
1867
+ # skip loopback addresses
1865
1868
  [[ "$proxy_host" == "127."* || "$proxy_host" == "localhost" ]] && return 0
1866
1869
 
1867
- # 解析为 IP
1870
+ # resolve to IP
1868
1871
  local proxy_ip
1869
1872
  proxy_ip=$(python3 -c "import socket; print(socket.gethostbyname('$proxy_host'))" 2>/dev/null || echo "$proxy_host")
1870
1873
 
@@ -1874,12 +1877,12 @@ _relay_add_route() {
1874
1877
  gateway=$(route -n get default 2>/dev/null | awk '/gateway:/{print $2}')
1875
1878
  [[ -z "$gateway" ]] && return 1
1876
1879
 
1877
- # 检查是否已有直连路由
1880
+ # check if direct route exists
1878
1881
  local current_gw
1879
1882
  current_gw=$(route -n get "$proxy_ip" 2>/dev/null | awk '/gateway:/{print $2}')
1880
1883
  [[ "$current_gw" == "$gateway" ]] && return 0
1881
1884
 
1882
- echo " 添加直连路由:$proxy_ip $gateway(需要 sudo"
1885
+ echo " adding direct route: $proxy_ip -> $gateway (needs sudo)"
1883
1886
  sudo route add -host "$proxy_ip" "$gateway" >/dev/null 2>&1 || return 1
1884
1887
  echo "$proxy_ip" > "$CAC_DIR/relay_route_ip"
1885
1888
 
@@ -1889,7 +1892,7 @@ _relay_add_route() {
1889
1892
  iface=$(ip route show default 2>/dev/null | awk '{print $5; exit}')
1890
1893
  [[ -z "$gateway" ]] && return 1
1891
1894
 
1892
- echo " 添加直连路由:$proxy_ip $gateway dev $iface(需要 sudo"
1895
+ echo " adding direct route: $proxy_ip -> $gateway dev $iface (needs sudo)"
1893
1896
  sudo ip route add "$proxy_ip/32" via "$gateway" dev "$iface" 2>/dev/null || return 1
1894
1897
  echo "$proxy_ip" > "$CAC_DIR/relay_route_ip"
1895
1898
  fi
@@ -1911,7 +1914,7 @@ _relay_remove_route() {
1911
1914
  rm -f "$route_file"
1912
1915
  }
1913
1916
 
1914
- # 检测 TUN 网卡是否活跃
1917
+ # detect if TUN interface is active
1915
1918
  _detect_tun_active() {
1916
1919
  local os; os=$(_detect_os)
1917
1920
  if [[ "$os" == "macos" ]]; then
@@ -1925,12 +1928,12 @@ _detect_tun_active() {
1925
1928
  fi
1926
1929
  }
1927
1930
 
1928
- # ── 用户命令 ─────────────────────────────────────────────────────
1931
+ # ── user commands ─────────────────────────────────────────────────────
1929
1932
 
1930
1933
  cmd_relay() {
1931
1934
  _require_setup
1932
1935
  local current; current=$(_current_env)
1933
- [[ -z "$current" ]] && { echo "错误:未激活环境,先运行 'cac <name>'" >&2; exit 1; }
1936
+ [[ -z "$current" ]] && { echo "error: no active environment, run 'cac <name>' first" >&2; exit 1; }
1934
1937
 
1935
1938
  local env_dir="$ENVS_DIR/$current"
1936
1939
  local action="${1:-status}"
@@ -1939,60 +1942,60 @@ cmd_relay() {
1939
1942
  case "$action" in
1940
1943
  on)
1941
1944
  echo "on" > "$env_dir/relay"
1942
- echo "$(_green "✓") Relay 已启用(环境:$(_bold "$current")"
1945
+ echo "$(_green "✓") Relay enabled (env: $(_bold "$current"))"
1943
1946
 
1944
- # --route 标志:添加直连路由
1947
+ # --route flag: add direct route
1945
1948
  if [[ "$flag" == "--route" ]]; then
1946
1949
  local proxy; proxy=$(_read "$env_dir/proxy")
1947
1950
  _relay_add_route "$proxy"
1948
1951
  fi
1949
1952
 
1950
- # 如果 relay 没在运行,启动它
1953
+ # start relay if not running
1951
1954
  if ! _relay_is_running; then
1952
- printf " 启动 relay ... "
1955
+ printf " starting relay ... "
1953
1956
  if _relay_start "$current"; then
1954
1957
  local port; port=$(_read "$CAC_DIR/relay.port")
1955
1958
  echo "$(_green "✓") 127.0.0.1:$port"
1956
1959
  else
1957
- echo "$(_red "✗ 启动失败")"
1960
+ echo "$(_red "✗ failed to start")"
1958
1961
  fi
1959
1962
  fi
1960
- echo " 下次启动 claude 时将自动通过本地中转连接代理"
1963
+ echo " next claude launch will automatically connect via local relay"
1961
1964
  ;;
1962
1965
  off)
1963
1966
  rm -f "$env_dir/relay"
1964
1967
  _relay_stop
1965
- echo "$(_green "✓") Relay 已停用(环境:$(_bold "$current")"
1968
+ echo "$(_green "✓") Relay disabled (env: $(_bold "$current"))"
1966
1969
  ;;
1967
1970
  status)
1968
1971
  if [[ -f "$env_dir/relay" ]] && [[ "$(_read "$env_dir/relay")" == "on" ]]; then
1969
- echo "Relay 模式:$(_green "已启用")"
1972
+ echo "Relay mode: $(_green "enabled")"
1970
1973
  else
1971
- echo "Relay 模式:未启用"
1974
+ echo "Relay mode: disabled"
1972
1975
  if _detect_tun_active; then
1973
- echo " $(_yellow "⚠") 检测到 TUN 模式,建议运行 'cac relay on'"
1976
+ echo " $(_yellow "⚠") TUN mode detected, consider running 'cac relay on'"
1974
1977
  fi
1975
1978
  return
1976
1979
  fi
1977
1980
 
1978
1981
  if _relay_is_running; then
1979
1982
  local pid; pid=$(_read "$CAC_DIR/relay.pid")
1980
- local port; port=$(_read "$CAC_DIR/relay.port" "未知")
1981
- echo "Relay 进程:$(_green "运行中") (PID=$pid, 端口=$port)"
1983
+ local port; port=$(_read "$CAC_DIR/relay.port" "unknown")
1984
+ echo "Relay process: $(_green "running") (PID=$pid, port=$port)"
1982
1985
  else
1983
- echo "Relay 进程:$(_yellow "未启动")(将在 claude 启动时自动启动)"
1986
+ echo "Relay process: $(_yellow "not started") (will auto-start with claude)"
1984
1987
  fi
1985
1988
 
1986
1989
  if [[ -f "$CAC_DIR/relay_route_ip" ]]; then
1987
1990
  local route_ip; route_ip=$(_read "$CAC_DIR/relay_route_ip")
1988
- echo "直连路由 :$route_ip"
1991
+ echo "Direct route: $route_ip"
1989
1992
  fi
1990
1993
  ;;
1991
1994
  *)
1992
- echo "用法:cac relay [on|off|status]" >&2
1993
- echo " on [--route] 启用本地中转(--route 添加直连路由绕过 TUN" >&2
1994
- echo " off 停用本地中转" >&2
1995
- echo " status 查看状态" >&2
1995
+ echo "usage: cac relay [on|off|status]" >&2
1996
+ echo " on [--route] enable local relay (--route adds direct route to bypass TUN)" >&2
1997
+ echo " off disable local relay" >&2
1998
+ echo " status show status" >&2
1996
1999
  ;;
1997
2000
  esac
1998
2001
  }
@@ -2196,17 +2199,17 @@ cmd_stop() {
2196
2199
 
2197
2200
  cmd_continue() {
2198
2201
  if [[ ! -f "$CAC_DIR/stopped" ]]; then
2199
- echo "cac 当前未停用,无需恢复"
2202
+ echo "cac is not stopped, no need to resume"
2200
2203
  return
2201
2204
  fi
2202
2205
 
2203
2206
  local current; current=$(_current_env)
2204
2207
  if [[ -z "$current" ]]; then
2205
- echo "错误:没有已激活的环境,运行 'cac <name>'" >&2; exit 1
2208
+ echo "error: no active environment, run 'cac <name>'" >&2; exit 1
2206
2209
  fi
2207
2210
 
2208
2211
  rm -f "$CAC_DIR/stopped"
2209
- echo "$(_green "✓") cac 已恢复当前环境:$(_bold "$current")"
2212
+ echo "$(_green "✓") cac resumedcurrent env: $(_bold "$current")"
2210
2213
  }
2211
2214
 
2212
2215
  # ━━━ cmd_claude.sh ━━━
@@ -2936,7 +2939,7 @@ cmd_docker() {
2936
2939
  }
2937
2940
 
2938
2941
  # ━━━ cmd_delete.sh ━━━
2939
- # ── cmd: delete(卸载)────────────────────────────────────────
2942
+ # ── cmd: delete (uninstall) ────────────────────────────────────────
2940
2943
 
2941
2944
  cmd_delete() {
2942
2945
  echo "=== cac delete ==="
@@ -2947,50 +2950,50 @@ cmd_delete() {
2947
2950
 
2948
2951
  _remove_path_from_rc "$rc_file"
2949
2952
 
2950
- # 停止 relay 进程和路由
2953
+ # stop relay processes and routes
2951
2954
  if [[ -d "$CAC_DIR" ]]; then
2952
2955
  _relay_stop 2>/dev/null || true
2953
2956
 
2954
- # 停止 docker port-forward 进程
2957
+ # stop docker port-forward processes
2955
2958
  if [[ -d /tmp/cac-docker-ports ]]; then
2956
2959
  for _pf in /tmp/cac-docker-ports/*.pid; do
2957
2960
  [[ -f "$_pf" ]] || continue
2958
2961
  kill "$(cat "$_pf")" 2>/dev/null || true
2959
2962
  rm -f "$_pf"
2960
2963
  done
2961
- echo " ✓ 已停止 docker port-forward 进程"
2964
+ echo " ✓ stopped docker port-forward processes"
2962
2965
  fi
2963
2966
 
2964
- # 兜底:清理可能残留的 relay 孤儿进程
2967
+ # fallback: clean up orphaned relay processes
2965
2968
  pkill -f "node.*\.cac/relay\.js" 2>/dev/null || true
2966
2969
 
2967
2970
  rm -rf "$CAC_DIR"
2968
- echo " ✓ 已删除 $CAC_DIR"
2971
+ echo " ✓ deleted $CAC_DIR"
2969
2972
  else
2970
- echo " - $CAC_DIR 不存在,跳过"
2973
+ echo " - $CAC_DIR does not exist, skipping"
2971
2974
  fi
2972
2975
 
2973
2976
  local method
2974
2977
  method=$(_install_method)
2975
2978
  echo
2976
2979
  if [[ "$method" == "npm" ]]; then
2977
- echo " ✓ 已清除所有 cac 数据和配置"
2980
+ echo " ✓ cleared all cac data and config"
2978
2981
  echo
2979
- echo "要完全卸载 cac 命令,请执行:"
2982
+ echo "to fully uninstall the cac command, run:"
2980
2983
  echo " npm uninstall -g claude-cac"
2981
2984
  else
2982
2985
  if [[ -f "$HOME/bin/cac" ]]; then
2983
2986
  rm -f "$HOME/bin/cac"
2984
- echo " ✓ 已删除 $HOME/bin/cac"
2987
+ echo " ✓ deleted $HOME/bin/cac"
2985
2988
  fi
2986
- echo " ✓ 卸载完成"
2989
+ echo " ✓ uninstall complete"
2987
2990
  fi
2988
2991
 
2989
2992
  echo
2990
2993
  if [[ -n "$rc_file" ]]; then
2991
- echo "请重开终端或执行 source $rc_file 使变更生效。"
2994
+ echo "please restart terminal or run source $rc_file for changes to take effect."
2992
2995
  else
2993
- echo "请重开终端使变更生效。"
2996
+ echo "please restart terminal for changes to take effect."
2994
2997
  fi
2995
2998
  }
2996
2999
 
@@ -3042,7 +3045,7 @@ cmd_help() {
3042
3045
  }
3043
3046
 
3044
3047
  # ━━━ main.sh ━━━
3045
- # ── 入口:分发命令 ──────────────────────────────────────────────
3048
+ # ── entry: dispatch commands ──────────────────────────────────────────────
3046
3049
 
3047
3050
  [[ $# -eq 0 ]] && { cmd_help; exit 0; }
3048
3051
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cac",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Isolate, protect, and manage your Claude Code — versions, environments, identity, and proxy.",
5
5
  "bin": {
6
6
  "cac": "cac"