@wangxt0223/codex-switcher 0.6.3 → 0.6.4

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.4 - 2026-04-12
4
+
5
+ - Updated `list` output columns to include `HOME` and a dedicated `SOURCE` column (`api`/`local`).
6
+ - Changed `LAST ACTIVITY` to absolute `MM-DD HH:MM` format and aligned weekly reset display to the same date+time style.
7
+ - Improved last-activity fallback behavior: when API omits activity timestamp, fallback to local session-derived time.
8
+ - Updated Chinese/English README and plugin docs for the latest `list` output schema.
9
+ - Updated smoke tests for `HOME`/`SOURCE` columns and absolute-time assertions.
10
+ - Removed accidental package self-dependency from `package.json`.
11
+
3
12
  ## 0.6.3 - 2026-04-12
4
13
 
5
14
  - Changed `list` email rendering to show plain email only (removed `(account)` prefix).
package/README.en.md CHANGED
@@ -81,7 +81,7 @@ Commands below use `codex-sw` (all are equivalent under `codex-switcher`):
81
81
  | Account | `codex-sw account logout [account] [--env <env>] [--target cli\|app\|both]` | Logout account |
82
82
  | Account | `codex-sw account current [cli\|app]` | Show current env/account pointer |
83
83
  | Usage proxy | `codex-sw proxy [<host:port>\|off\|test]` | Configure/test usage API proxy (only affects `list`) |
84
- | Query/Run | `codex-sw list` | Show `ENV/ACCOUNT/EMAIL/PLAN/5H/WEEKLY/LAST ACTIVITY` |
84
+ | Query/Run | `codex-sw list` | Show `ENV/HOME/ACCOUNT/EMAIL/PLAN/5H/WEEKLY/LAST ACTIVITY/SOURCE` (time format: `MM-DD HH:MM`) |
85
85
  | Query/Run | `codex-sw status` | Show login status for current CLI/App pointers |
86
86
  | Query/Run | `codex-sw current [cli\|app]` | Show current env/account |
87
87
  | Query/Run | `codex-sw exec -- <codex args...>` | Run `codex` under current CLI env/account |
@@ -101,6 +101,7 @@ Commands below use `codex-sw` (all are equivalent under `codex-switcher`):
101
101
  | Maintenance | `codex-sw init [--shell zsh\|bash] [--dry-run]` | Initialize PATH bootstrap |
102
102
  | Maintenance | `codex-sw upgrade [--dry-run]` | Upgrade from npm |
103
103
  | Maintenance | `codex-sw recover [--dry-run]` | Recover corrupted pointers |
104
+ | Maintenance | `codex-sw version` | Print current tool version |
104
105
  | Maintenance | `codex-sw check` | Basic health checks |
105
106
  | Maintenance | `codex-sw doctor [--fix]` | Deep diagnostics and optional auto-fix |
106
107
  | Maintenance | `codex-sw --help` | Show full help |
package/README.md CHANGED
@@ -78,7 +78,7 @@ codex-switcher account use corp --env project-a
78
78
  | 账号管理 | `codex-sw account logout [account] [--env <env>] [--target cli\|app\|both]` | 注销账号(删除对应 auth) |
79
79
  | 账号管理 | `codex-sw account current [cli\|app]` | 查看当前 env/account 指针 |
80
80
  | 用量代理 | `codex-sw proxy [<host:port>\|off\|test]` | 设置/关闭/测试“用量 API”代理(仅影响 `list`) |
81
- | 查询执行 | `codex-sw list` | 展示 `ENV/ACCOUNT/EMAIL/PLAN/5H/WEEKLY/LAST ACTIVITY` |
81
+ | 查询执行 | `codex-sw list` | 展示 `ENV/HOME/ACCOUNT/EMAIL/PLAN/5H/WEEKLY/LAST ACTIVITY/SOURCE`(时间为 `MM-DD HH:MM`) |
82
82
  | 查询执行 | `codex-sw status` | 检查 CLI/App 当前登录状态 |
83
83
  | 查询执行 | `codex-sw current [cli\|app]` | 查看当前 env/account |
84
84
  | 查询执行 | `codex-sw exec -- <codex args...>` | 在当前 CLI env/account 下执行 `codex` |
@@ -98,6 +98,7 @@ codex-switcher account use corp --env project-a
98
98
  | 维护命令 | `codex-sw init [--shell zsh\|bash] [--dry-run]` | 初始化 PATH 快捷命令 |
99
99
  | 维护命令 | `codex-sw upgrade [--dry-run]` | 升级到最新 npm 版本 |
100
100
  | 维护命令 | `codex-sw recover [--dry-run]` | 自动恢复损坏指针 |
101
+ | 维护命令 | `codex-sw version` | 输出当前工具版本号 |
101
102
  | 维护命令 | `codex-sw check` | 基础健康检查 |
102
103
  | 维护命令 | `codex-sw doctor [--fix]` | 深度检查并可选自动修复 |
103
104
  | 维护命令 | `codex-sw --help` | 查看完整帮助 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wangxt0223/codex-switcher",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
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,8 +43,5 @@
43
43
  "publishConfig": {
44
44
  "access": "public",
45
45
  "registry": "https://registry.npmjs.org/"
46
- },
47
- "dependencies": {
48
- "@wangxt0223/codex-switcher": "^0.6.0"
49
46
  }
50
47
  }
@@ -49,6 +49,8 @@ codex-switcher app logout [account]
49
49
  codex-switcher app status
50
50
  codex-switcher app stop
51
51
  codex-switcher app current
52
+
53
+ codex-switcher version
52
54
  ```
53
55
 
54
56
  ## Typical Flow
@@ -72,13 +74,15 @@ codex-switcher account use corp --env project-a
72
74
 
73
75
  `codex-switcher list` prints:
74
76
 
75
- `ENV / ACCOUNT / EMAIL / PLAN / 5H USAGE / WEEKLY USAGE / LAST ACTIVITY`
77
+ `ENV / HOME / ACCOUNT / EMAIL / PLAN / 5H USAGE / WEEKLY USAGE / LAST ACTIVITY / SOURCE`
76
78
 
77
79
  Usage data strategy:
78
80
 
79
81
  - API first (`chatgpt.com/backend-api/wham/usage`)
80
82
  - Auto fallback to local `sessions/*.jsonl` on API failure
81
- - `LAST ACTIVITY` appends source marker `(api)` or `(local)`
83
+ - `5H USAGE` / `WEEKLY USAGE` reset time is unified to `MM-DD HH:MM` (example: `89% (04-19 11:45)`)
84
+ - `LAST ACTIVITY` uses absolute `MM-DD HH:MM` format
85
+ - `SOURCE` is shown as a dedicated column (`api` or `local`)
82
86
  - You can configure a dedicated proxy for this usage API via `codex-switcher proxy 127.0.0.1:7899` (only affects `list`)
83
87
  - If no manual proxy is configured, env/system proxy settings are auto-detected for usage API requests
84
88
 
@@ -49,6 +49,8 @@ codex-switcher app logout [account]
49
49
  codex-switcher app status
50
50
  codex-switcher app stop
51
51
  codex-switcher app current
52
+
53
+ codex-switcher version
52
54
  ```
53
55
 
54
56
  ## 典型流程
@@ -72,13 +74,15 @@ codex-switcher account use corp --env project-a
72
74
 
73
75
  `codex-switcher list` 默认输出:
74
76
 
75
- `ENV / ACCOUNT / EMAIL / PLAN / 5H USAGE / WEEKLY USAGE / LAST ACTIVITY`
77
+ `ENV / HOME / ACCOUNT / EMAIL / PLAN / 5H USAGE / WEEKLY USAGE / LAST ACTIVITY / SOURCE`
76
78
 
77
79
  其中用量数据策略为:
78
80
 
79
81
  - 默认优先 API(`chatgpt.com/backend-api/wham/usage`)
80
82
  - API 失败自动回退本地 `sessions/*.jsonl`
81
- - `LAST ACTIVITY` 列会追加 `(api)` `(local)` 标记来源
83
+ - `5H USAGE` / `WEEKLY USAGE` 的重置时间统一为 `MM-DD HH:MM`(例如 `89% (04-19 11:45)`)
84
+ - `LAST ACTIVITY` 使用绝对时间 `MM-DD HH:MM`
85
+ - `SOURCE` 独立显示 `api` 或 `local`
82
86
  - 可通过 `codex-switcher proxy 127.0.0.1:7899` 为该用量 API 单独配置代理(仅影响 `list`)
83
87
  - 未手动设置时会自动检测环境变量/系统代理并用于用量 API 请求
84
88
 
@@ -75,6 +75,7 @@ Usage:
75
75
  codex-sw init [--shell zsh|bash] [--dry-run]
76
76
  codex-sw upgrade [--dry-run]
77
77
  codex-sw recover [--dry-run]
78
+ codex-sw version
78
79
  codex-sw check
79
80
  codex-sw doctor [--fix]
80
81
 
@@ -86,7 +87,7 @@ Notes:
86
87
  - Same-env account switch only swaps auth.json and does not sync shared data.
87
88
  - `proxy` only affects usage API calls used by `list`.
88
89
  - If manual proxy is not set, usage API proxy is auto-detected from env/system proxy settings.
89
- - `list` prints: EMAIL / PLAN / 5H USAGE / WEEKLY USAGE / LAST ACTIVITY (+ env/account context).
90
+ - `list` prints: ENV / HOME / ACCOUNT / EMAIL / PLAN / 5H USAGE / WEEKLY USAGE / LAST ACTIVITY / SOURCE.
90
91
  - Usage metrics are API-first and automatically fallback to local sessions; source is shown as (api|local).
91
92
  USAGE
92
93
  }
@@ -314,6 +315,19 @@ clear_usage_proxy() {
314
315
  rm -f -- "$USAGE_PROXY_FILE"
315
316
  }
316
317
 
318
+ display_path_for_output() {
319
+ local path="$1"
320
+ if [[ "$path" == "$HOME" ]]; then
321
+ echo "~"
322
+ return
323
+ fi
324
+ if [[ "$path" == "$HOME/"* ]]; then
325
+ echo "~/${path#$HOME/}"
326
+ return
327
+ fi
328
+ echo "$path"
329
+ }
330
+
317
331
  set_current_env() {
318
332
  local target="$1"
319
333
  local env="$2"
@@ -1132,9 +1146,9 @@ PY
1132
1146
 
1133
1147
  cmd_list() {
1134
1148
  local cli_env app_env cli_account app_account
1135
- local env account marks auth_file home metrics
1149
+ local env account marks auth_file home home_display metrics
1136
1150
  local email plan usage_5h usage_weekly last_activity source
1137
- local usage_proxy="" usage_info="" usage_proxy_source=""
1151
+ local usage_proxy="" usage_info=""
1138
1152
 
1139
1153
  cli_env="$(read_current_env cli || true)"
1140
1154
  app_env="$(read_current_env app || true)"
@@ -1142,12 +1156,13 @@ cmd_list() {
1142
1156
  app_account="$(read_current_account app || true)"
1143
1157
  usage_info="$(resolve_usage_proxy_with_source || true)"
1144
1158
  if [[ -n "$usage_info" ]]; then
1145
- IFS=$'\t' read -r usage_proxy_source usage_proxy <<< "$usage_info"
1159
+ IFS=$'\t' read -r _ usage_proxy <<< "$usage_info"
1146
1160
  fi
1147
1161
 
1148
- printf '%-12s %-30s %-44s %-10s %-16s %-18s %s\n' "ENV" "ACCOUNT" "EMAIL" "PLAN" "5H USAGE" "WEEKLY USAGE" "LAST ACTIVITY"
1162
+ printf '%-12s %-28s %-30s %-38s %-10s %-20s %-20s %-16s %s\n' "ENV" "HOME" "ACCOUNT" "EMAIL" "PLAN" "5H USAGE" "WEEKLY USAGE" "LAST ACTIVITY" "SOURCE"
1149
1163
  while IFS= read -r env; do
1150
1164
  home="$(env_home_path "$env")"
1165
+ home_display="$(display_path_for_output "$home")"
1151
1166
  ensure_env_home "$env"
1152
1167
  local found=0
1153
1168
  while IFS= read -r account; do
@@ -1175,9 +1190,8 @@ cmd_list() {
1175
1190
  last_activity="${last_activity:--}"
1176
1191
  source="${source:-local}"
1177
1192
  [[ "$source" == "api" || "$source" == "local" ]] || source="local"
1178
- last_activity="${last_activity} (${source})"
1179
1193
 
1180
- printf '%-12s %-30s %-44s %-10s %-16s %-18s %s\n' "$env" "$account$marks" "$email" "$plan" "$usage_5h" "$usage_weekly" "$last_activity"
1194
+ printf '%-12s %-28s %-30s %-38s %-10s %-20s %-20s %-16s %s\n' "$env" "$home_display" "$account$marks" "$email" "$plan" "$usage_5h" "$usage_weekly" "$last_activity" "$source"
1181
1195
  done < <(list_accounts_for_env "$env")
1182
1196
 
1183
1197
  if [[ "$found" -eq 0 ]]; then
@@ -1198,8 +1212,7 @@ cmd_list() {
1198
1212
  last_activity="${last_activity:--}"
1199
1213
  source="${source:-local}"
1200
1214
  [[ "$source" == "api" || "$source" == "local" ]] || source="local"
1201
- last_activity="${last_activity} (${source})"
1202
- printf '%-12s %-30s %-44s %-10s %-16s %-18s %s\n' "$env" "-" "$email" "$plan" "$usage_5h" "$usage_weekly" "$last_activity"
1215
+ printf '%-12s %-28s %-30s %-38s %-10s %-20s %-20s %-16s %s\n' "$env" "$home_display" "-" "$email" "$plan" "$usage_5h" "$usage_weekly" "$last_activity" "$source"
1203
1216
  fi
1204
1217
  done < <(list_env_names | sort -u)
1205
1218
  }
@@ -1605,6 +1618,10 @@ cmd_check() {
1605
1618
  echo "check: ok"
1606
1619
  }
1607
1620
 
1621
+ cmd_version() {
1622
+ echo "$(switcher_version)"
1623
+ }
1624
+
1608
1625
  cmd_doctor() {
1609
1626
  local fix="${1:-false}"
1610
1627
  local issues=0
@@ -2178,6 +2195,10 @@ main() {
2178
2195
  fi
2179
2196
  with_lock cmd_recover "$dry"
2180
2197
  ;;
2198
+ version)
2199
+ [[ "$#" -eq 0 ]] || err "usage: $SCRIPT_NAME version"
2200
+ cmd_version
2201
+ ;;
2181
2202
  check)
2182
2203
  [[ "$#" -eq 0 ]] || err "usage: $SCRIPT_NAME check"
2183
2204
  cmd_check
@@ -6,7 +6,6 @@ import json
6
6
  import os
7
7
  import subprocess
8
8
  import sys
9
- import time
10
9
  from typing import Any, Dict, Optional
11
10
 
12
11
 
@@ -223,22 +222,15 @@ def format_usage(window: Optional[Dict[str, Any]]) -> str:
223
222
  percent = clamp_percent(float(window["remaining_percent"]))
224
223
  reset_epoch = window.get("reset_epoch")
225
224
  if isinstance(reset_epoch, int):
226
- reset_text = dt.datetime.fromtimestamp(reset_epoch).strftime("%H:%M")
225
+ reset_text = dt.datetime.fromtimestamp(reset_epoch).strftime("%m-%d %H:%M")
227
226
  return f"{percent}% ({reset_text})"
228
227
  return f"{percent}%"
229
228
 
230
229
 
231
- def format_relative(last_epoch: Optional[int], now_epoch: int) -> str:
230
+ def format_last_activity(last_epoch: Optional[int]) -> str:
232
231
  if not isinstance(last_epoch, int):
233
232
  return DASH
234
- delta = max(0, now_epoch - last_epoch)
235
- if delta < 60:
236
- return "just now"
237
- if delta < 3600:
238
- return f"{delta // 60}m ago"
239
- if delta < 86400:
240
- return f"{delta // 3600}h ago"
241
- return f"{delta // 86400}d ago"
233
+ return dt.datetime.fromtimestamp(last_epoch).strftime("%m-%d %H:%M")
242
234
 
243
235
 
244
236
  def collect_local_metrics(data_path: str) -> Dict[str, Any]:
@@ -326,7 +318,6 @@ def collect_api_metrics(access_token: str, account_id: str, usage_proxy: str, ti
326
318
 
327
319
  def main() -> int:
328
320
  args = parse_args()
329
- now_epoch = int(time.time())
330
321
  auth_data = load_json(args.auth_file)
331
322
  tokens = auth_data.get("tokens")
332
323
  if not isinstance(tokens, dict):
@@ -352,7 +343,8 @@ def main() -> int:
352
343
  plan_from_claims = normalize_plan(auth_data.get("chatgpt_plan_type") or auth_data.get("plan_type"))
353
344
 
354
345
  api_metrics = collect_api_metrics(access_token, account_id, args.usage_proxy, args.timeout_seconds)
355
- metrics = api_metrics if api_metrics is not None else collect_local_metrics(args.data_path)
346
+ local_metrics = collect_local_metrics(args.data_path)
347
+ metrics = api_metrics if api_metrics is not None else local_metrics
356
348
 
357
349
  windows = metrics.get("windows")
358
350
  if not isinstance(windows, dict):
@@ -374,8 +366,12 @@ def main() -> int:
374
366
 
375
367
  last_activity_epoch = metrics.get("last_activity_epoch")
376
368
  if not isinstance(last_activity_epoch, int):
377
- last_activity_epoch = None
378
- last_activity = format_relative(last_activity_epoch, now_epoch)
369
+ fallback_last_activity = local_metrics.get("last_activity_epoch")
370
+ if isinstance(fallback_last_activity, int):
371
+ last_activity_epoch = fallback_last_activity
372
+ else:
373
+ last_activity_epoch = None
374
+ last_activity = format_last_activity(last_activity_epoch)
379
375
 
380
376
  if email:
381
377
  display_email = email
@@ -99,9 +99,11 @@ echo '{"memo":"persist"}' > "$DEFAULT_HOME/shared.json"
99
99
  check_out="$("$SW" check)"
100
100
  echo "$check_out" | grep -Eq '^version: [0-9]+\.[0-9]+\.[0-9]+$'
101
101
  echo "$check_out" | grep -q "check: ok"
102
+ echo "$("$SW" version)" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'
102
103
  link_check_out="$("$SW_LINK" check)"
103
104
  echo "$link_check_out" | grep -Eq '^version: [0-9]+\.[0-9]+\.[0-9]+$'
104
105
  echo "$link_check_out" | grep -q "check: ok"
106
+ echo "$("$SW_LINK" version)" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'
105
107
  [[ "$("$SW" proxy)" == "usage_api_proxy: off" ]]
106
108
  "$SW" proxy 127.0.0.1:7899
107
109
  [[ "$("$SW" proxy)" == "usage_api_proxy: http://127.0.0.1:7899 (manual)" ]]
@@ -174,21 +176,25 @@ grep -q '{"shared":"project"}' "$ENVS/project/home/shared.json"
174
176
 
175
177
  "$SW" list >/tmp/codex_sw_list_api
176
178
  grep -q "ENV" /tmp/codex_sw_list_api
179
+ grep -q "HOME" /tmp/codex_sw_list_api
177
180
  grep -q "ACCOUNT" /tmp/codex_sw_list_api
178
181
  grep -q "EMAIL" /tmp/codex_sw_list_api
179
182
  grep -q "PLAN" /tmp/codex_sw_list_api
180
183
  grep -q "5H USAGE" /tmp/codex_sw_list_api
181
184
  grep -q "WEEKLY USAGE" /tmp/codex_sw_list_api
182
185
  grep -q "LAST ACTIVITY" /tmp/codex_sw_list_api
186
+ grep -q "SOURCE" /tmp/codex_sw_list_api
183
187
  grep -q "personal@example.com" /tmp/codex_sw_list_api
184
- grep -q "(api)" /tmp/codex_sw_list_api
188
+ grep -Eq "[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}[[:space:]]+api$" /tmp/codex_sw_list_api
185
189
  grep -q "60% (" /tmp/codex_sw_list_api
186
190
  grep -q "80% (" /tmp/codex_sw_list_api
191
+ grep -Eq "60% \\([0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}\\)" /tmp/codex_sw_list_api
192
+ grep -Eq "80% \\([0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}\\)" /tmp/codex_sw_list_api
187
193
  grep -q "proxy=http://127.0.0.1:7899" "$CODEX_SWITCHER_TEST_CURL_LOG"
188
194
 
189
195
  "$SW_LINK" list >/tmp/codex_sw_list_symlink
190
196
  grep -q "personal@example.com" /tmp/codex_sw_list_symlink
191
- grep -q "(api)" /tmp/codex_sw_list_symlink
197
+ grep -Eq "[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}[[:space:]]+api$" /tmp/codex_sw_list_symlink
192
198
 
193
199
  mkdir -p "$ENVS/project/home/sessions/2026/04/12"
194
200
  cat > "$ENVS/project/home/sessions/2026/04/12/rollout-test.jsonl" <<'JSONL'
@@ -199,9 +205,11 @@ export CODEX_SWITCHER_TEST_CURL_MODE="fail"
199
205
  [[ "$("$SW" proxy)" == "usage_api_proxy: off" ]]
200
206
  : > "$CODEX_SWITCHER_TEST_CURL_LOG"
201
207
  "$SW" list >/tmp/codex_sw_list_local
202
- grep -q "(local)" /tmp/codex_sw_list_local
208
+ grep -Eq "[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}[[:space:]]+local$" /tmp/codex_sw_list_local
203
209
  grep -q "75% (" /tmp/codex_sw_list_local
204
210
  grep -q "30% (" /tmp/codex_sw_list_local
211
+ grep -Eq "75% \\([0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}\\)" /tmp/codex_sw_list_local
212
+ grep -Eq "30% \\([0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}\\)" /tmp/codex_sw_list_local
205
213
  grep -q "^proxy=$" "$CODEX_SWITCHER_TEST_CURL_LOG"
206
214
 
207
215
  export CODEX_SWITCHER_TEST_CURL_MODE="success"