@wangxt0223/codex-switcher 0.6.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1 - 2026-04-12
4
+
5
+ - Fixed symlink invocation path resolution for `codex-sw` / `codex-switcher`, so global npm installs can always find bundled scripts.
6
+ - Fixed `list` row field reuse bug that could leak previous row values into later rows.
7
+ - Improved usage API request compatibility by adding `ChatGPT-Account-Id` and browser-like headers, while keeping local sessions fallback.
8
+ - Added smoke-test coverage for symlink launch path behavior.
9
+
3
10
  ## 0.6.0 - 2026-04-12
4
11
 
5
12
  - Refactored core model from profile-based switching to `env + account` with built-in `default=~/.codex`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wangxt0223/codex-switcher",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Env + account switcher for Codex CLI and Codex App with shared env data and per-account auth.",
5
5
  "license": "MIT",
6
6
  "author": "wangxt",
@@ -43,5 +43,8 @@
43
43
  "publishConfig": {
44
44
  "access": "public",
45
45
  "registry": "https://registry.npmjs.org/"
46
+ },
47
+ "dependencies": {
48
+ "@wangxt0223/codex-switcher": "^0.6.0"
46
49
  }
47
50
  }
@@ -1,5 +1,17 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
3
+
4
+ resolve_script_dir() {
5
+ local src="${BASH_SOURCE[0]}"
6
+ local dir
7
+ while [[ -h "$src" ]]; do
8
+ dir="$(cd -P "$(dirname "$src")" && pwd)"
9
+ src="$(readlink "$src")"
10
+ [[ "$src" == /* ]] || src="$dir/$src"
11
+ done
12
+ cd -P "$(dirname "$src")" && pwd
13
+ }
14
+
15
+ SCRIPT_DIR="$(resolve_script_dir)"
4
16
  export CODEX_SWITCHER_INVOKED_AS="codex-sw"
5
17
  exec "$SCRIPT_DIR/codex-switcher" "$@"
@@ -1,6 +1,19 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
+ resolve_script_dir() {
5
+ local src="${BASH_SOURCE[0]}"
6
+ local dir
7
+ while [[ -h "$src" ]]; do
8
+ dir="$(cd -P "$(dirname "$src")" && pwd)"
9
+ src="$(readlink "$src")"
10
+ [[ "$src" == /* ]] || src="$dir/$src"
11
+ done
12
+ cd -P "$(dirname "$src")" && pwd
13
+ }
14
+
15
+ SCRIPT_DIR="$(resolve_script_dir)"
16
+
4
17
  STATE_DIR="${CODEX_SWITCHER_STATE_DIR:-$HOME/.codex-switcher}"
5
18
  ENVS_DIR="${CODEX_SWITCHER_ENVS_DIR:-$HOME/.codex-envs}"
6
19
  ACCOUNTS_DIR="${CODEX_SWITCHER_ACCOUNTS_DIR:-$STATE_DIR/env-accounts}"
@@ -85,9 +98,8 @@ now_utc() {
85
98
  }
86
99
 
87
100
  switcher_version() {
88
- local script_dir package_json version
89
- script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
90
- package_json="$(cd "$script_dir/../../.." && pwd)/package.json"
101
+ local package_json version
102
+ package_json="$(cd "$SCRIPT_DIR/../../.." && pwd)/package.json"
91
103
 
92
104
  if [[ -f "$package_json" ]]; then
93
105
  version="$(sed -nE 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$package_json" | head -n 1)"
@@ -567,9 +579,7 @@ app_stop_managed() {
567
579
  }
568
580
 
569
581
  metrics_script_path() {
570
- local script_dir
571
- script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
572
- echo "$script_dir/profile-metrics.py"
582
+ echo "$SCRIPT_DIR/profile-metrics.py"
573
583
  }
574
584
 
575
585
  collect_profile_metrics_tsv() {
@@ -931,6 +941,12 @@ cmd_list() {
931
941
  local found=0
932
942
  while IFS= read -r account; do
933
943
  found=1
944
+ email=""
945
+ plan=""
946
+ usage_5h=""
947
+ usage_weekly=""
948
+ last_activity=""
949
+ source=""
934
950
  marks=""
935
951
  [[ "$env" == "$cli_env" && "$account" == "$cli_account" ]] && marks="${marks} [cli-current]"
936
952
  [[ "$env" == "$app_env" && "$account" == "$app_account" ]] && marks="${marks} [app-current]"
@@ -954,6 +970,12 @@ cmd_list() {
954
970
  done < <(list_accounts_for_env "$env")
955
971
 
956
972
  if [[ "$found" -eq 0 ]]; then
973
+ email=""
974
+ plan=""
975
+ usage_5h=""
976
+ usage_weekly=""
977
+ last_activity=""
978
+ source=""
957
979
  metrics="$(collect_profile_metrics_tsv "-" "$home/auth.json" "$home" || true)"
958
980
  if [[ -n "$metrics" ]]; then
959
981
  IFS=$'\t' read -r email plan usage_5h usage_weekly last_activity source <<< "$metrics"
@@ -1242,7 +1264,7 @@ cmd_init() {
1242
1264
  ;;
1243
1265
  esac
1244
1266
 
1245
- script_abs="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
1267
+ script_abs="$SCRIPT_DIR/codex-switcher"
1246
1268
  target_link="$HOME/.local/bin/codex-sw"
1247
1269
  block_start="# >>> codex-sw init >>>"
1248
1270
  block_end="# <<< codex-sw init <<<"
@@ -11,6 +11,10 @@ from typing import Any, Dict, Optional
11
11
 
12
12
 
13
13
  USAGE_ENDPOINT = "https://chatgpt.com/backend-api/wham/usage"
14
+ DEFAULT_USER_AGENT = (
15
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
16
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
17
+ )
14
18
  DASH = "-"
15
19
  KNOWN_PLANS = {"free", "plus", "pro", "team", "business", "enterprise", "edu"}
16
20
 
@@ -165,7 +169,7 @@ def extract_windows_from_usage_blob(blob: Dict[str, Any]) -> Dict[int, Dict[str,
165
169
  return windows
166
170
 
167
171
 
168
- def request_usage(access_token: str, timeout_seconds: int) -> Optional[Dict[str, Any]]:
172
+ def request_usage(access_token: str, account_id: str, timeout_seconds: int) -> Optional[Dict[str, Any]]:
169
173
  if not access_token:
170
174
  return None
171
175
  cmd = [
@@ -177,8 +181,14 @@ def request_usage(access_token: str, timeout_seconds: int) -> Optional[Dict[str,
177
181
  str(max(2, timeout_seconds + 2)),
178
182
  "-H",
179
183
  f"Authorization: Bearer {access_token}",
180
- USAGE_ENDPOINT,
184
+ "-H",
185
+ "Accept: application/json",
186
+ "-H",
187
+ f"User-Agent: {DEFAULT_USER_AGENT}",
181
188
  ]
189
+ if account_id:
190
+ cmd.extend(["-H", f"ChatGPT-Account-Id: {account_id}"])
191
+ cmd.append(USAGE_ENDPOINT)
182
192
  try:
183
193
  result = subprocess.run(
184
194
  cmd,
@@ -276,8 +286,8 @@ def collect_local_metrics(data_path: str) -> Dict[str, Any]:
276
286
  return {"source": "local"}
277
287
 
278
288
 
279
- def collect_api_metrics(access_token: str, timeout_seconds: int) -> Optional[Dict[str, Any]]:
280
- payload = request_usage(access_token, timeout_seconds)
289
+ def collect_api_metrics(access_token: str, account_id: str, timeout_seconds: int) -> Optional[Dict[str, Any]]:
290
+ payload = request_usage(access_token, account_id, timeout_seconds)
281
291
  if not payload:
282
292
  return None
283
293
 
@@ -318,6 +328,9 @@ def main() -> int:
318
328
  access_token = tokens.get("access_token")
319
329
  if not isinstance(access_token, str):
320
330
  access_token = ""
331
+ account_id = tokens.get("account_id")
332
+ if not isinstance(account_id, str):
333
+ account_id = ""
321
334
  id_token = tokens.get("id_token")
322
335
  if not isinstance(id_token, str):
323
336
  id_token = ""
@@ -331,7 +344,7 @@ def main() -> int:
331
344
  if plan_from_claims == "unknown":
332
345
  plan_from_claims = normalize_plan(auth_data.get("chatgpt_plan_type") or auth_data.get("plan_type"))
333
346
 
334
- api_metrics = collect_api_metrics(access_token, args.timeout_seconds)
347
+ api_metrics = collect_api_metrics(access_token, account_id, args.timeout_seconds)
335
348
  metrics = api_metrics if api_metrics is not None else collect_local_metrics(args.data_path)
336
349
 
337
350
  windows = metrics.get("windows")
@@ -3,6 +3,7 @@ set -euo pipefail
3
3
 
4
4
  ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
5
  SW="$ROOT/scripts/codex-sw"
6
+ SW_LINK=""
6
7
 
7
8
  bash -n "$SW"
8
9
 
@@ -12,6 +13,8 @@ ENVS="$TMPBASE/envs"
12
13
  DEFAULT_HOME="$TMPBASE/default-home"
13
14
  BIN="$TMPBASE/bin"
14
15
  mkdir -p "$BIN" "$DEFAULT_HOME"
16
+ SW_LINK="$BIN/codex-sw-link"
17
+ ln -s "$SW" "$SW_LINK"
15
18
 
16
19
  cleanup() {
17
20
  pkill -f "$BIN/fake-codex-app" >/dev/null 2>&1 || true
@@ -91,6 +94,9 @@ echo '{"memo":"persist"}' > "$DEFAULT_HOME/shared.json"
91
94
  check_out="$("$SW" check)"
92
95
  echo "$check_out" | grep -Eq '^version: [0-9]+\.[0-9]+\.[0-9]+$'
93
96
  echo "$check_out" | grep -q "check: ok"
97
+ link_check_out="$("$SW_LINK" check)"
98
+ echo "$link_check_out" | grep -Eq '^version: [0-9]+\.[0-9]+\.[0-9]+$'
99
+ echo "$link_check_out" | grep -q "check: ok"
94
100
  init_out="$("$SW" init --dry-run)"
95
101
  echo "$init_out" | grep -q "\[dry-run\]"
96
102
  "$SW" upgrade
@@ -171,6 +177,10 @@ grep -q "(api)" /tmp/codex_sw_list_api
171
177
  grep -q "60% (" /tmp/codex_sw_list_api
172
178
  grep -q "80% (" /tmp/codex_sw_list_api
173
179
 
180
+ "$SW_LINK" list >/tmp/codex_sw_list_symlink
181
+ grep -q "(personal)personal@example.com" /tmp/codex_sw_list_symlink
182
+ grep -q "(api)" /tmp/codex_sw_list_symlink
183
+
174
184
  mkdir -p "$ENVS/project/home/sessions/2026/04/12"
175
185
  cat > "$ENVS/project/home/sessions/2026/04/12/rollout-test.jsonl" <<'JSONL'
176
186
  {"timestamp":"2026-04-12T09:00:00Z","type":"event_msg","payload":{"type":"token_count","rate_limits":{"plan_type":"business","primary":{"used_percent":25,"window_minutes":300,"resets_at":1776004200},"secondary":{"used_percent":70,"window_minutes":10080,"resets_at":1776519000}}}}