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