claude-cac 1.5.0-beta.2 → 1.5.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -110,10 +110,10 @@ cac ls # = cac env ls
110
110
  | `cac claude ls` | 列出已安装版本 |
111
111
  | `cac claude pin <ver>` | 当前环境绑定版本 |
112
112
  | **环境管理** | |
113
- | `cac env create <name> [-p proxy] [-c ver] [--clone]` | 创建环境(自动激活,`--clone` 继承配置) |
113
+ | `cac env create <name> [-p proxy] [-c ver] [--clone] [--telemetry mode] [--persona preset]` | 创建环境(自动激活,`--telemetry transparent/stealth/paranoid` 控制遥测,`--persona macos-vscode/...` 用于容器) |
114
114
  | `cac env ls` | 列出环境 |
115
115
  | `cac env rm <name>` | 删除环境 |
116
- | `cac env set [name] <key> <value>` | 修改环境(proxy / version) |
116
+ | `cac env set [name] <key> <value>` | 修改环境(proxy / version / telemetry / persona) |
117
117
  | `cac env check [-d]` | 验证当前环境(`-d` 显示详情) |
118
118
  | `cac <name>` | 激活环境 |
119
119
  | **自管理** | |
@@ -293,10 +293,10 @@ Each environment is fully isolated:
293
293
  | `cac claude ls` | List installed versions |
294
294
  | `cac claude pin <ver>` | Pin current env to version |
295
295
  | **Environment management** | |
296
- | `cac env create <name> [-p proxy] [-c ver] [--clone]` | Create environment (auto-activates, `--clone` inherits config) |
296
+ | `cac env create <name> [-p proxy] [-c ver] [--clone] [--telemetry mode] [--persona preset]` | Create environment (auto-activates, `--telemetry transparent/stealth/paranoid` for telemetry control, `--persona macos-vscode/...` for containers) |
297
297
  | `cac env ls` | List environments |
298
298
  | `cac env rm <name>` | Remove environment |
299
- | `cac env set [name] <key> <value>` | Modify environment (proxy / version) |
299
+ | `cac env set [name] <key> <value>` | Modify environment (proxy / version / telemetry / persona) |
300
300
  | `cac env check [-d]` | Verify current environment (`-d` for details) |
301
301
  | `cac <name>` | Activate environment |
302
302
  | **Self-management** | |
package/cac CHANGED
@@ -11,7 +11,7 @@ VERSIONS_DIR="$CAC_DIR/versions"
11
11
  # ── utils: colors, read/write, UUID, proxy parsing ───────────────────────
12
12
 
13
13
  # shellcheck disable=SC2034 # used in build-concatenated cac script
14
- CAC_VERSION="1.5.0-beta.2"
14
+ CAC_VERSION="1.5.1-beta.1"
15
15
 
16
16
  _read() { [[ -f "$1" ]] && tr -d '[:space:]' < "$1" || echo "${2:-}"; }
17
17
  _die() { printf '%b\n' "$(_red "error:") $*" >&2; exit 1; }
@@ -63,7 +63,7 @@ _new_hostname() { echo "host-$(_gen_uuid | cut -d- -f1 | tr '[:upper:]' '[:lower
63
63
  _new_mac() { printf '02:%02x:%02x:%02x:%02x:%02x' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)); }
64
64
  _new_git_remote() { echo "https://github.com/user-$(_gen_uuid | cut -d- -f1)/project-$(_gen_uuid | cut -d- -f2).git"; }
65
65
  _new_git_email() { echo "user-$(_gen_uuid | cut -d- -f1 | tr '[:upper:]' '[:lower:]')@users.noreply.github.com"; }
66
- _new_device_token() { python3 -c "import os; print(os.urandom(32).hex())" || _die "python3 required"; }
66
+ _new_device_token() { _new_user_id; }
67
67
 
68
68
  # Get real command path (bypass shim)
69
69
  _get_real_cmd() {
@@ -1232,12 +1232,10 @@ if [[ -n "$PROXY" ]]; then
1232
1232
  fi
1233
1233
 
1234
1234
  # ── git identity spoofing ──
1235
- # Prevent git email leakage (Claude runs `git config --get user.email` on startup)
1235
+ # Intercept `git config --get user.email` at process level (telemetry read only)
1236
+ # Do NOT set GIT_AUTHOR_EMAIL/GIT_COMMITTER_EMAIL — those would affect real git commits
1236
1237
  if [[ -f "$_env_dir/git_email" ]]; then
1237
- _git_email=$(tr -d '[:space:]' < "$_env_dir/git_email")
1238
- export GIT_AUTHOR_EMAIL="$_git_email"
1239
- export GIT_COMMITTER_EMAIL="$_git_email"
1240
- export CAC_GIT_EMAIL="$_git_email"
1238
+ export CAC_GIT_EMAIL=$(tr -d '[:space:]' < "$_env_dir/git_email")
1241
1239
  fi
1242
1240
 
1243
1241
  # ── repository fingerprint (rh) spoofing ──
@@ -1255,31 +1253,29 @@ fi
1255
1253
  # ── persona (Docker/server environment spoofing) ──
1256
1254
  if [[ -f "$_env_dir/persona" ]]; then
1257
1255
  _persona=$(tr -d '[:space:]' < "$_env_dir/persona")
1256
+ export TERM="xterm-256color"
1258
1257
  case "$_persona" in
1259
1258
  macos-vscode)
1260
1259
  export TERM_PROGRAM="vscode"
1261
1260
  export VSCODE_GIT_ASKPASS_MAIN="/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass-main.js"
1262
1261
  export __CFBundleIdentifier="com.microsoft.VSCode"
1263
- export TERM="xterm-256color"
1264
1262
  ;;
1265
1263
  macos-cursor)
1266
1264
  export TERM_PROGRAM="vscode"
1267
- export CURSOR_TRACE_ID="cursor-$(head -c 8 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n')"
1265
+ [[ -f "$_env_dir/cursor_trace_id" ]] || printf 'cursor-%s' "$(od -An -tx1 -N8 /dev/urandom | tr -d ' \n')" > "$_env_dir/cursor_trace_id"
1266
+ export CURSOR_TRACE_ID=$(tr -d '[:space:]' < "$_env_dir/cursor_trace_id")
1268
1267
  export __CFBundleIdentifier="com.todesktop.230313mzl4w4u92"
1269
- export TERM="xterm-256color"
1270
1268
  ;;
1271
1269
  macos-iterm)
1272
1270
  export TERM_PROGRAM="iTerm.app"
1273
1271
  export __CFBundleIdentifier="com.googlecode.iterm2"
1274
- export TERM="xterm-256color"
1275
- export ITERM_SESSION_ID="w0t0p0:$(head -c 16 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n')"
1272
+ [[ -f "$_env_dir/iterm_session_id" ]] || printf 'w0t0p0:%s' "$(od -An -tx1 -N16 /dev/urandom | tr -d ' \n')" > "$_env_dir/iterm_session_id"
1273
+ export ITERM_SESSION_ID=$(tr -d '[:space:]' < "$_env_dir/iterm_session_id")
1276
1274
  ;;
1277
1275
  linux-desktop)
1278
1276
  export TERM_PROGRAM="vscode"
1279
- export TERM="xterm-256color"
1280
1277
  ;;
1281
1278
  esac
1282
- # Hide Docker signals when persona is active
1283
1279
  export CAC_HIDE_DOCKER=1
1284
1280
  fi
1285
1281
 
@@ -1393,6 +1389,40 @@ if [[ -n "$PROXY" ]] && [[ -f "$CAC_DIR/relay.js" ]]; then
1393
1389
  echo "$_rport" > "$_relay_port_file"
1394
1390
  fi
1395
1391
 
1392
+ # env-level watchdog singleton: auto-restarts relay if it crashes
1393
+ # - one watchdog per environment, shared across all sessions
1394
+ # - exits automatically when relay is intentionally stopped (relay.proxy removed)
1395
+ _relay_watchdog_file="$CAC_DIR/relay.watchdog.pid"
1396
+ _wd_running=false
1397
+ if [[ -f "$_relay_watchdog_file" ]]; then
1398
+ _wpid=$(tr -d '[:space:]' < "$_relay_watchdog_file")
1399
+ [[ -n "$_wpid" ]] && kill -0 "$_wpid" 2>/dev/null && _wd_running=true
1400
+ fi
1401
+ if [[ "$_wd_running" != "true" ]]; then
1402
+ (
1403
+ trap 'rm -f "$CAC_DIR/relay.watchdog.pid"' EXIT
1404
+ set +e
1405
+ while true; do
1406
+ sleep 5
1407
+ # relay.proxy removed by _relay_stop — intentional stop, exit watchdog
1408
+ [[ -f "$CAC_DIR/relay.proxy" ]] || exit 0
1409
+ # relay alive — nothing to do
1410
+ if [[ -f "$CAC_DIR/relay.pid" ]]; then
1411
+ _rpid=$(tr -d '[:space:]' < "$CAC_DIR/relay.pid")
1412
+ kill -0 "$_rpid" 2>/dev/null && continue
1413
+ fi
1414
+ # relay dead — restart on same port with same proxy
1415
+ _rport=$(tr -d '[:space:]' < "$CAC_DIR/relay.port" 2>/dev/null || true)
1416
+ _rproxy=$(tr -d '[:space:]' < "$CAC_DIR/relay.proxy" 2>/dev/null || true)
1417
+ [[ -n "$_rport" ]] && [[ -n "$_rproxy" ]] || exit 0
1418
+ node "$CAC_DIR/relay.js" "$_rport" "$_rproxy" "$CAC_DIR/relay.pid" </dev/null >>"$CAC_DIR/relay.log" 2>&1 &
1419
+ done
1420
+ ) &
1421
+ _new_wpid=$!
1422
+ echo "$_new_wpid" > "$_relay_watchdog_file"
1423
+ disown "$_new_wpid"
1424
+ fi
1425
+
1396
1426
  # override proxy to point to local relay
1397
1427
  if [[ -f "$_relay_port_file" ]]; then
1398
1428
  _rport=$(tr -d '[:space:]' < "$_relay_port_file")
@@ -1632,7 +1662,7 @@ _env_cmd_create() {
1632
1662
  esac
1633
1663
  done
1634
1664
 
1635
- [[ -n "$name" ]] || _die "usage: cac env create <name> [-p <proxy>] [-c <version>]"
1665
+ [[ -n "$name" ]] || _die "usage: cac env create <name> [-p <proxy>] [-c <version>] [--telemetry <mode>] [--persona <preset>]"
1636
1666
  [[ "$name" =~ ^[a-zA-Z0-9_-]+$ ]] || _die "invalid name '$name' (use alphanumeric, dash, underscore)"
1637
1667
 
1638
1668
  local env_dir="$ENVS_DIR/$name"
@@ -1924,7 +1954,9 @@ _env_cmd_set() {
1924
1954
  echo " $(_green "set") [name] proxy --remove Remove proxy"
1925
1955
  echo " $(_green "set") [name] version <ver|latest> Change Claude version"
1926
1956
  echo " $(_green "set") [name] telemetry <stealth|paranoid|transparent>"
1957
+ echo " Telemetry blocking: stealth (1p_events only), paranoid (max), transparent (none)"
1927
1958
  echo " $(_green "set") [name] persona <macos-vscode|macos-cursor|macos-iterm|linux-desktop|--remove>"
1959
+ echo " Terminal preset: inject desktop env vars, hide Docker signals (for containers)"
1928
1960
  echo
1929
1961
  echo " $(_dim "If name is omitted, uses the current active environment.")"
1930
1962
  echo
@@ -2025,10 +2057,10 @@ cmd_env() {
2025
2057
  echo
2026
2058
  echo " $(_bold "cac env") — environment management"
2027
2059
  echo
2028
- echo " $(_green "create") <name> [-p proxy] [-c ver] [--clone [source]] [--no-link] [--telemetry mode] [--persona preset]"
2029
- echo " $(_green "set") [name] proxy <url> Set proxy"
2030
- echo " $(_green "set") [name] proxy --remove Remove proxy"
2031
- echo " $(_green "set") [name] version <ver|latest> Change Claude version"
2060
+ echo " $(_green "create") <name> [-p proxy] [-c ver] [--telemetry mode] [--persona preset]"
2061
+ echo " Create isolated environment (auto-activates)"
2062
+ echo " $(_green "set") [name] <key> <value> Modify environment"
2063
+ echo " proxy, version, telemetry, or persona"
2032
2064
  echo " $(_green "ls") List all environments"
2033
2065
  echo " $(_green "rm") <name> Remove an environment"
2034
2066
  echo " $(_green "check") Verify current environment"
@@ -2106,6 +2138,15 @@ _relay_stop() {
2106
2138
  fi
2107
2139
  rm -f "$CAC_DIR/relay.port" "$CAC_DIR/relay.proxy"
2108
2140
 
2141
+ # stop watchdog (relay.proxy already removed above, watchdog will self-exit within 5s;
2142
+ # kill it immediately for clean teardown)
2143
+ local wd_file="$CAC_DIR/relay.watchdog.pid"
2144
+ if [[ -f "$wd_file" ]]; then
2145
+ local wd_pid; wd_pid=$(tr -d '[:space:]' < "$wd_file")
2146
+ [[ -n "$wd_pid" ]] && kill "$wd_pid" 2>/dev/null || true
2147
+ rm -f "$wd_file"
2148
+ fi
2149
+
2109
2150
  # cleanup route
2110
2151
  _relay_remove_route 2>/dev/null || true
2111
2152
  }
@@ -115,11 +115,7 @@ if (fakeGitRemote) {
115
115
  const _origExecSyncFp = child_process.execSync.bind(child_process);
116
116
  child_process.execSync = function(cmd, options) {
117
117
  var cmdStr = typeof cmd === 'string' ? cmd : cmd.toString();
118
- if (isGitRemoteCmd(cmdStr)) {
119
- var result = fakeGitRemote + '\n';
120
- return (typeof options === 'string' || (options && options.encoding))
121
- ? result : Buffer.from(result);
122
- }
118
+ if (isGitRemoteCmd(cmdStr)) return fakeResult(options, fakeGitRemote + '\n');
123
119
  return _origExecSyncFp(cmd, options);
124
120
  };
125
121
 
@@ -130,11 +126,7 @@ if (fakeGitRemote) {
130
126
  var cb = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null;
131
127
  if (isGitRemoteCmd(cmdStr)) {
132
128
  if (cb) process.nextTick(cb, null, fakeGitRemote + '\n', '');
133
- var { EventEmitter } = require('events');
134
- var cp = new EventEmitter();
135
- cp.stdout = new EventEmitter(); cp.stderr = new EventEmitter();
136
- cp.stdin = null; cp.pid = 0; cp.kill = function() { return false; };
137
- return cp;
129
+ return makeFakeChildProcess();
138
130
  }
139
131
  return _origExecFp.apply(child_process, args);
140
132
  };
@@ -145,9 +137,7 @@ if (fakeGitRemote) {
145
137
  var fullCmd = file + ' ' + fileArgs.join(' ');
146
138
  if (isGitRemoteCmd(fullCmd)) {
147
139
  var opts = Array.isArray(argsOrOpts) ? options : argsOrOpts;
148
- var result = fakeGitRemote + '\n';
149
- return (typeof opts === 'string' || (opts && opts.encoding))
150
- ? result : Buffer.from(result);
140
+ return fakeResult(opts, fakeGitRemote + '\n');
151
141
  }
152
142
  return _origExecFileSyncFp(file, argsOrOpts, options);
153
143
  };
@@ -158,14 +148,11 @@ if (fakeGitRemote) {
158
148
  // Intercept to prevent real email leakage (wrapper also sets GIT_AUTHOR_EMAIL)
159
149
  const fakeGitEmail = process.env.CAC_GIT_EMAIL;
160
150
  if (fakeGitEmail) {
161
- // Re-wrap execSync if not already wrapped for git remote
162
151
  var _prevExecSync = child_process.execSync;
163
152
  child_process.execSync = function(cmd, options) {
164
153
  var cmdStr = typeof cmd === 'string' ? cmd : cmd.toString();
165
154
  if (/git\s+config\s+(--global\s+|--get\s+)*user\.email/i.test(cmdStr)) {
166
- var result = fakeGitEmail + '\n';
167
- return (typeof options === 'string' || (options && options.encoding))
168
- ? result : Buffer.from(result);
155
+ return fakeResult(options, fakeGitEmail + '\n');
169
156
  }
170
157
  return _prevExecSync(cmd, options);
171
158
  };
@@ -183,20 +170,18 @@ if (process.env.CAC_HIDE_DOCKER === '1') {
183
170
  };
184
171
 
185
172
  // Intercept /proc/1/cgroup reads to remove docker references
186
- if (fakeMachineId || true) {
187
- var _prevReadFileSync = fs.readFileSync;
188
- fs.readFileSync = function(path, options) {
189
- var ps = typeof path === 'string' ? path : (path && path.toString ? path.toString() : '');
190
- if (ps === '/proc/1/cgroup') {
191
- var content;
192
- try { content = _prevReadFileSync(path, options); } catch(e) { throw e; }
193
- var str = typeof content === 'string' ? content : content.toString();
194
- str = str.replace(/docker|containerd|kubepods/gi, 'system.slice');
195
- return (typeof options === 'string' || (options && options.encoding)) ? str : Buffer.from(str);
196
- }
197
- return _prevReadFileSync(path, options);
198
- };
199
- }
173
+ var _prevReadFileSync = fs.readFileSync;
174
+ fs.readFileSync = function(path, options) {
175
+ var ps = typeof path === 'string' ? path : (path && path.toString ? path.toString() : '');
176
+ if (ps === '/proc/1/cgroup') {
177
+ var content;
178
+ try { content = _prevReadFileSync(path, options); } catch(e) { throw e; }
179
+ var str = typeof content === 'string' ? content : content.toString();
180
+ str = str.replace(/docker|containerd|kubepods/gi, 'system.slice');
181
+ return fakeResult(options, str);
182
+ }
183
+ return _prevReadFileSync(path, options);
184
+ };
200
185
  }
201
186
 
202
187
  // --- Windows: intercept child_process for wmic / reg queries ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cac",
3
- "version": "1.5.0-beta.2",
3
+ "version": "1.5.1-beta.1",
4
4
  "description": "Isolate, protect, and manage your Claude Code — versions, environments, identity, and proxy.",
5
5
  "bin": {
6
6
  "cac": "cac"