codex-workspaces 0.3.1__tar.gz → 0.3.3__tar.gz

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 (34) hide show
  1. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/CHANGELOG.md +35 -0
  2. {codex_workspaces-0.3.1/src/codex_workspaces.egg-info → codex_workspaces-0.3.3}/PKG-INFO +70 -2
  3. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/README.MD +69 -1
  4. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/README.zh-CN.md +69 -1
  5. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/docs/DESIGN.zh-CN.md +18 -1
  6. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/docs/RELEASE.zh-CN.md +2 -2
  7. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/docs/TESTING.zh-CN.md +40 -2
  8. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/pyproject.toml +1 -1
  9. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/__init__.py +1 -1
  10. codex_workspaces-0.3.3/src/codex_workspaces/auth_inspector.py +125 -0
  11. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/cli.py +156 -4
  12. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/config.py +3 -0
  13. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/core.py +1024 -17
  14. codex_workspaces-0.3.3/src/codex_workspaces/private_api/__init__.py +10 -0
  15. codex_workspaces-0.3.3/src/codex_workspaces/private_api/auth.py +45 -0
  16. codex_workspaces-0.3.3/src/codex_workspaces/private_api/client.py +150 -0
  17. codex_workspaces-0.3.3/src/codex_workspaces/private_api/errors.py +52 -0
  18. codex_workspaces-0.3.3/src/codex_workspaces/private_api/models.py +95 -0
  19. codex_workspaces-0.3.3/src/codex_workspaces/stats.py +311 -0
  20. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/store.py +53 -2
  21. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3/src/codex_workspaces.egg-info}/PKG-INFO +70 -2
  22. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/SOURCES.txt +6 -0
  23. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/tests/test_cli.py +59 -4
  24. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/tests/test_core.py +426 -3
  25. codex_workspaces-0.3.1/src/codex_workspaces/stats.py +0 -170
  26. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/MANIFEST.in +0 -0
  27. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/setup.cfg +0 -0
  28. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/__main__.py +0 -0
  29. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/errors.py +0 -0
  30. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/platforms.py +0 -0
  31. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/dependency_links.txt +0 -0
  32. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/entry_points.txt +0 -0
  33. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/requires.txt +0 -0
  34. {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/top_level.txt +0 -0
@@ -4,6 +4,41 @@ All notable changes to `codex-workspaces` will be documented in this file.
4
4
 
5
5
  This project follows a simple changelog format while it is still pre-release.
6
6
 
7
+ ## 0.3.3 - 2026-07-04
8
+
9
+ ### Added
10
+
11
+ - Added best-effort local `auth.json` metadata inspection for account email, account ID, user ID, organization ID, plan, and auth hash without private API calls.
12
+ - Added `accounts refresh-meta <account>|--all [--overwrite]` for manually refreshing parsed account metadata.
13
+ - Added `accounts export` and `accounts import` backup support with meta-only exports by default, explicit `--include-auth` credential export, dry-run import plans, conflict renaming, overwrite with pre-import backup, auth hash validation, and unsafe tar path rejection.
14
+ - Added enhanced local `stats` views for summary, daily usage, top models, workspace aggregation, account aggregation, JSON output, and Markdown output.
15
+ - Added experimental private API provider scaffolding for realtime account quota lookup.
16
+ - Added `quota` for the current active account and `accounts quota <account>` for a specific managed account.
17
+ - Added `accounts list -a` / `accounts list --all-with-quota` for realtime quota display across managed accounts.
18
+ - Added `accounts refresh [account|--all]` for refreshing remote account metadata and quota cache without modifying `auth.json`.
19
+ - Added `config get/set experimental_private_api.*` for explicitly enabling and configuring experimental private API behavior.
20
+ - Added quota cache with TTL and auth-hash invalidation under `~/.codex-workspaces/cache/quota/`.
21
+
22
+ ### Changed
23
+
24
+ - Improved account list/info metadata enrichment while keeping auth parsing optional and non-blocking.
25
+ - Improved local stats presentation while keeping all statistics local and read-only.
26
+ - Updated README, design, testing, release, and changelog docs for the 0.3.3 workflow.
27
+
28
+ ### Security
29
+
30
+ - Added explicit warnings for exports that include `auth.json` credentials.
31
+ - Added backup archive validation against path traversal, symlinks, device files, and auth hash mismatches during import.
32
+ - Kept auth inspection local-only: no token refresh, no private API calls, no network requests, and no sensitive token-like fields in normal output.
33
+ - Private API quota/refresh features are disabled by default and must be explicitly enabled.
34
+ - Sensitive auth material is redacted from private API errors, command output, and quota cache.
35
+ - Quota cache stores no tokens, cookies, authorization headers, or raw `auth.json`.
36
+
37
+ ### Notes
38
+
39
+ - Realtime quota and refresh depend on experimental private Codex/OpenAI interfaces and may break when upstream behavior changes.
40
+ - `stats` remains local historical usage; `quota` is realtime remote lookup and is intentionally separate.
41
+
7
42
  ## 0.3.1 - 2026-07-04
8
43
 
9
44
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-workspaces
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Cross-platform Codex workspace switcher with a preserved macOS shell workflow.
5
5
  Author: blockchain-project-lives
6
6
  Project-URL: Homepage, https://github.com/blockchain-project-lives/codex-workspaces
@@ -47,10 +47,12 @@ On macOS, the Python CLI preserves the original app workflow: stop Codex App, sw
47
47
  - Initialize workspace directories with metadata.
48
48
  - Add a new account through an isolated temporary login workspace with `accounts add --login`.
49
49
  - Temporarily switch the current workspace account with `accounts use`, then restore the workspace default account.
50
+ - Best-effort parse local `auth.json` metadata such as email/account identifiers without network calls.
51
+ - Export and import account snapshot backups with explicit warnings for credential-bearing archives.
50
52
  - Inspect workspace/account metadata and run account-focused `doctor` diagnostics.
51
53
  - Migrate older `~/.codex-<name>` workspaces and import legacy `~/.codex-accounts` auth snapshots.
52
54
  - Keep macOS Codex App stop/start/restart support.
53
- - Show local token usage stats from Codex `state_*.sqlite` in read-only mode.
55
+ - Show local token usage stats from Codex `state_*.sqlite` in read-only mode, including daily, model, workspace, account, JSON, and Markdown views.
54
56
  - Block unsafe operations from a detected Codex built-in terminal when they cannot be delegated safely.
55
57
  - Support English and Chinese output through `CODEX_WORKSPACES_LANG`.
56
58
  - Package as a Python project with tests, CI, and PyPI publishing workflow.
@@ -159,6 +161,19 @@ codex-workspaces accounts add research --login
159
161
 
160
162
  This switches `~/.codex` to a temporary `login-<account>` workspace, lets you log in, saves the generated `auth.json` as `acct_<account>`, and restores the previous workspace. If login is interrupted, clean stale temporary workspaces with `codex-workspaces accounts cleanup-login-temp`.
161
163
 
164
+ Account metadata such as `email`, `account_id`, `user_id`, `organization_id`, and `plan` is parsed from local `auth.json` on a best-effort basis. Parsing failures are ignored, sensitive token-like fields are never printed, and no private API or network request is used.
165
+
166
+ Export or import account backups:
167
+
168
+ ```bash
169
+ codex-workspaces accounts export accounts.tar.gz --all
170
+ codex-workspaces accounts export accounts-with-auth.tar.gz --all --include-auth --yes
171
+ codex-workspaces accounts import accounts-with-auth.tar.gz --dry-run
172
+ codex-workspaces accounts import accounts-with-auth.tar.gz --rename-conflicts
173
+ ```
174
+
175
+ Backups created with `--include-auth` contain Codex credentials from `auth.json`. Store them securely and never commit them to git. Without `--include-auth`, export writes metadata only.
176
+
162
177
  Import legacy `codex-accounts` AUTH-mode snapshots:
163
178
 
164
179
  ```bash
@@ -187,9 +202,62 @@ codex-workspaces current
187
202
  codex-workspaces info work
188
203
  codex-workspaces doctor
189
204
  codex-workspaces stats
205
+ codex-workspaces stats summary --days 30
206
+ codex-workspaces stats daily --format markdown
207
+ codex-workspaces stats models --format json
208
+ codex-workspaces stats workspaces
209
+ codex-workspaces stats accounts
190
210
  codex-workspaces stats work --days 14
191
211
  ```
192
212
 
213
+ `stats` only reads local SQLite state. Missing or unrecognized workspace/account/model data is shown as `unknown`; results are best-effort and depend on the local Codex files available in each workspace.
214
+
215
+ ## Experimental Realtime Quota
216
+
217
+ `codex-workspaces` can optionally query realtime quota for managed accounts through an experimental private API provider.
218
+
219
+ This feature is disabled by default. It may break when upstream Codex/OpenAI internals change, may return `401`/`403`/`429`, and is not required for workspace or account switching.
220
+
221
+ Enable explicitly:
222
+
223
+ ```bash
224
+ codex-workspaces config set experimental_private_api.enabled true
225
+ codex-workspaces config set experimental_private_api.quota_enabled true
226
+ codex-workspaces config set experimental_private_api.refresh_enabled true
227
+ ```
228
+
229
+ Query current account quota:
230
+
231
+ ```bash
232
+ codex-workspaces quota
233
+ codex-workspaces quota --json
234
+ ```
235
+
236
+ Query a specific managed account:
237
+
238
+ ```bash
239
+ codex-workspaces accounts quota acct_work
240
+ ```
241
+
242
+ Query all accounts with quota:
243
+
244
+ ```bash
245
+ codex-workspaces accounts list -a
246
+ codex-workspaces accounts list --all-with-quota --json
247
+ ```
248
+
249
+ Refresh remote account metadata and quota cache:
250
+
251
+ ```bash
252
+ codex-workspaces accounts refresh
253
+ codex-workspaces accounts refresh acct_work
254
+ codex-workspaces accounts refresh --all --json
255
+ ```
256
+
257
+ Realtime quota uses explicit experimental configuration, timeout, serial account iteration, and a local TTL cache under `~/.codex-workspaces/cache/quota/`. The cache stores quota summaries and auth hashes only; it does not store tokens, cookies, authorization headers, or raw `auth.json`.
258
+
259
+ `stats` and `quota` are different: `stats` is local historical usage from SQLite, while `quota` is a realtime remote lookup. Quota failures do not affect local workspace/account switching.
260
+
193
261
  Switch accounts within the current workspace:
194
262
 
195
263
  ```bash
@@ -19,10 +19,12 @@ On macOS, the Python CLI preserves the original app workflow: stop Codex App, sw
19
19
  - Initialize workspace directories with metadata.
20
20
  - Add a new account through an isolated temporary login workspace with `accounts add --login`.
21
21
  - Temporarily switch the current workspace account with `accounts use`, then restore the workspace default account.
22
+ - Best-effort parse local `auth.json` metadata such as email/account identifiers without network calls.
23
+ - Export and import account snapshot backups with explicit warnings for credential-bearing archives.
22
24
  - Inspect workspace/account metadata and run account-focused `doctor` diagnostics.
23
25
  - Migrate older `~/.codex-<name>` workspaces and import legacy `~/.codex-accounts` auth snapshots.
24
26
  - Keep macOS Codex App stop/start/restart support.
25
- - Show local token usage stats from Codex `state_*.sqlite` in read-only mode.
27
+ - Show local token usage stats from Codex `state_*.sqlite` in read-only mode, including daily, model, workspace, account, JSON, and Markdown views.
26
28
  - Block unsafe operations from a detected Codex built-in terminal when they cannot be delegated safely.
27
29
  - Support English and Chinese output through `CODEX_WORKSPACES_LANG`.
28
30
  - Package as a Python project with tests, CI, and PyPI publishing workflow.
@@ -131,6 +133,19 @@ codex-workspaces accounts add research --login
131
133
 
132
134
  This switches `~/.codex` to a temporary `login-<account>` workspace, lets you log in, saves the generated `auth.json` as `acct_<account>`, and restores the previous workspace. If login is interrupted, clean stale temporary workspaces with `codex-workspaces accounts cleanup-login-temp`.
133
135
 
136
+ Account metadata such as `email`, `account_id`, `user_id`, `organization_id`, and `plan` is parsed from local `auth.json` on a best-effort basis. Parsing failures are ignored, sensitive token-like fields are never printed, and no private API or network request is used.
137
+
138
+ Export or import account backups:
139
+
140
+ ```bash
141
+ codex-workspaces accounts export accounts.tar.gz --all
142
+ codex-workspaces accounts export accounts-with-auth.tar.gz --all --include-auth --yes
143
+ codex-workspaces accounts import accounts-with-auth.tar.gz --dry-run
144
+ codex-workspaces accounts import accounts-with-auth.tar.gz --rename-conflicts
145
+ ```
146
+
147
+ Backups created with `--include-auth` contain Codex credentials from `auth.json`. Store them securely and never commit them to git. Without `--include-auth`, export writes metadata only.
148
+
134
149
  Import legacy `codex-accounts` AUTH-mode snapshots:
135
150
 
136
151
  ```bash
@@ -159,9 +174,62 @@ codex-workspaces current
159
174
  codex-workspaces info work
160
175
  codex-workspaces doctor
161
176
  codex-workspaces stats
177
+ codex-workspaces stats summary --days 30
178
+ codex-workspaces stats daily --format markdown
179
+ codex-workspaces stats models --format json
180
+ codex-workspaces stats workspaces
181
+ codex-workspaces stats accounts
162
182
  codex-workspaces stats work --days 14
163
183
  ```
164
184
 
185
+ `stats` only reads local SQLite state. Missing or unrecognized workspace/account/model data is shown as `unknown`; results are best-effort and depend on the local Codex files available in each workspace.
186
+
187
+ ## Experimental Realtime Quota
188
+
189
+ `codex-workspaces` can optionally query realtime quota for managed accounts through an experimental private API provider.
190
+
191
+ This feature is disabled by default. It may break when upstream Codex/OpenAI internals change, may return `401`/`403`/`429`, and is not required for workspace or account switching.
192
+
193
+ Enable explicitly:
194
+
195
+ ```bash
196
+ codex-workspaces config set experimental_private_api.enabled true
197
+ codex-workspaces config set experimental_private_api.quota_enabled true
198
+ codex-workspaces config set experimental_private_api.refresh_enabled true
199
+ ```
200
+
201
+ Query current account quota:
202
+
203
+ ```bash
204
+ codex-workspaces quota
205
+ codex-workspaces quota --json
206
+ ```
207
+
208
+ Query a specific managed account:
209
+
210
+ ```bash
211
+ codex-workspaces accounts quota acct_work
212
+ ```
213
+
214
+ Query all accounts with quota:
215
+
216
+ ```bash
217
+ codex-workspaces accounts list -a
218
+ codex-workspaces accounts list --all-with-quota --json
219
+ ```
220
+
221
+ Refresh remote account metadata and quota cache:
222
+
223
+ ```bash
224
+ codex-workspaces accounts refresh
225
+ codex-workspaces accounts refresh acct_work
226
+ codex-workspaces accounts refresh --all --json
227
+ ```
228
+
229
+ Realtime quota uses explicit experimental configuration, timeout, serial account iteration, and a local TTL cache under `~/.codex-workspaces/cache/quota/`. The cache stores quota summaries and auth hashes only; it does not store tokens, cookies, authorization headers, or raw `auth.json`.
230
+
231
+ `stats` and `quota` are different: `stats` is local historical usage from SQLite, while `quota` is a realtime remote lookup. Quota failures do not affect local workspace/account switching.
232
+
165
233
  Switch accounts within the current workspace:
166
234
 
167
235
  ```bash
@@ -19,10 +19,12 @@
19
19
  - 初始化带元数据的工作区目录。
20
20
  - 通过隔离的临时登录工作区执行 `accounts add --login`,新增账号而不退出当前账号。
21
21
  - 通过 `accounts use` 临时切换当前工作区账号,并可恢复工作区默认账号。
22
+ - 从本地 `auth.json` best-effort 解析 email/account 等元信息,不发网络请求。
23
+ - 导出和导入账号快照备份包;包含 auth 的备份会明确提示凭据风险。
22
24
  - 查看工作区/账号元数据,并通过 `doctor` 做账号诊断。
23
25
  - 迁移旧版 `~/.codex-<name>` 工作区,并导入旧 `~/.codex-accounts` 账号快照。
24
26
  - 保留 macOS 上 Codex App 的 `stop`、`start`、`restart`。
25
- - 以只读方式读取 Codex `state_*.sqlite`,展示本地 token 用量统计。
27
+ - 以只读方式读取 Codex `state_*.sqlite`,展示每日、模型、工作区、账号、JSON 和 Markdown 形式的本地 token 统计。
26
28
  - 在检测到 Codex 内置 Terminal 且无法安全转交时,阻止危险操作。
27
29
  - 支持中英文输出,可通过 `CODEX_WORKSPACES_LANG` 指定。
28
30
  - 提供 Python 包、测试、GitHub CI 和 PyPI 发布工作流。
@@ -131,6 +133,19 @@ codex-workspaces accounts add research --login
131
133
 
132
134
  它会把 `~/.codex` 临时切到 `login-<账号>` 工作区,让你登录新账号;登录生成 `auth.json` 后保存为 `acct_<账号>`,再恢复原工作区。如果登录中断,可用 `codex-workspaces accounts cleanup-login-temp` 清理残留临时工作区。
133
135
 
136
+ 账号元信息如 `email`、`account_id`、`user_id`、`organization_id`、`plan` 会从本地 `auth.json` best-effort 解析。解析失败不会影响账号功能,不会打印 token/secret/cookie 等敏感字段,也不会调用私有接口或发起网络请求。
137
+
138
+ 导出或导入账号备份:
139
+
140
+ ```bash
141
+ codex-workspaces accounts export accounts.tar.gz --all
142
+ codex-workspaces accounts export accounts-with-auth.tar.gz --all --include-auth --yes
143
+ codex-workspaces accounts import accounts-with-auth.tar.gz --dry-run
144
+ codex-workspaces accounts import accounts-with-auth.tar.gz --rename-conflicts
145
+ ```
146
+
147
+ 使用 `--include-auth` 创建的备份包包含 `auth.json` 里的 Codex 凭据,必须安全保存,不能提交到 git。不带 `--include-auth` 时只导出 meta,不包含 auth。
148
+
134
149
  导入旧 `codex-accounts` 的 AUTH 模式账号快照:
135
150
 
136
151
  ```bash
@@ -159,9 +174,62 @@ codex-workspaces current
159
174
  codex-workspaces info work
160
175
  codex-workspaces doctor
161
176
  codex-workspaces stats
177
+ codex-workspaces stats summary --days 30
178
+ codex-workspaces stats daily --format markdown
179
+ codex-workspaces stats models --format json
180
+ codex-workspaces stats workspaces
181
+ codex-workspaces stats accounts
162
182
  codex-workspaces stats work --days 14
163
183
  ```
164
184
 
185
+ `stats` 只读取本地 SQLite 状态文件。无法识别的 workspace/account/model 会显示为 `unknown`;统计结果取决于各工作区里实际存在的 Codex 本地文件。
186
+
187
+ ## 实验性实时额度查询
188
+
189
+ `codex-workspaces` 可以通过实验性 private API provider 查询已管理账号的实时额度。
190
+
191
+ 该功能默认关闭,依赖 Codex/OpenAI 内部接口,可能随上游变化而失效,也可能返回 `401`/`403`/`429`。它不是 workspace/account 本地切换的依赖。
192
+
193
+ 显式开启:
194
+
195
+ ```bash
196
+ codex-workspaces config set experimental_private_api.enabled true
197
+ codex-workspaces config set experimental_private_api.quota_enabled true
198
+ codex-workspaces config set experimental_private_api.refresh_enabled true
199
+ ```
200
+
201
+ 查询当前账号额度:
202
+
203
+ ```bash
204
+ codex-workspaces quota
205
+ codex-workspaces quota --json
206
+ ```
207
+
208
+ 查询指定账号额度:
209
+
210
+ ```bash
211
+ codex-workspaces accounts quota acct_work
212
+ ```
213
+
214
+ 查询所有账号额度:
215
+
216
+ ```bash
217
+ codex-workspaces accounts list -a
218
+ codex-workspaces accounts list --all-with-quota --json
219
+ ```
220
+
221
+ 刷新账号远端信息和 quota cache:
222
+
223
+ ```bash
224
+ codex-workspaces accounts refresh
225
+ codex-workspaces accounts refresh acct_work
226
+ codex-workspaces accounts refresh --all --json
227
+ ```
228
+
229
+ 实时额度能力使用显式实验配置、请求超时、串行账号遍历和本地 TTL 缓存,缓存目录为 `~/.codex-workspaces/cache/quota/`。缓存只保存额度摘要和 auth hash,不保存 token、cookie、authorization header 或原始 `auth.json`。
230
+
231
+ `stats` 和 `quota` 不同:`stats` 是本地 SQLite 历史统计,`quota` 是实时远端查询。额度查询失败不会影响本地 workspace/account 切换。
232
+
165
233
  在当前工作区临时切换账号:
166
234
 
167
235
  ```bash
@@ -85,11 +85,19 @@ Windows:
85
85
  - 切换 workspace 前会保存当前 live `auth.json` 到 `active_account_id` 对应账号快照。
86
86
  - `accounts use` 只修改当前 workspace 的 `active_account_id`,不修改 `default_account_id`。
87
87
  - 进入 workspace 时按策略恢复账号:`workspace-default` 恢复默认账号,`last-active` 恢复该 workspace 上次活跃账号,`keep-current` 尽量沿用刚才正在使用的账号。
88
+ - `auth.json` 元信息解析只做本地 best-effort 递归扫描,用于补全 email/account_id/user_id/organization_id/plan/auth_hash;解析失败不影响切换和账号功能。
89
+ - auth 解析不会打印 token、secret、credential、authorization、refresh、access、cookie 等敏感字段,不刷新 token,不调用私有接口,不发网络请求。
90
+ - `accounts export` 默认只导出 meta;必须显式 `--include-auth` 并确认后才导出 `auth.json` 凭据。
91
+ - `accounts import` 解包前校验 tar 条目,拒绝路径穿越、绝对路径、symlink、硬链接和设备文件;导入 auth 后保持 0600 权限,覆盖前写入备份。
88
92
  - `migrate --dry-run` 只打印计划,不创建目录、不写元数据。
89
93
  - `migrate` 会先备份当前 `~/.codex`、旧 workspace 和旧账号目录,再复制到统一目录;旧目录不会被自动删除。
90
94
  - 如果当前 `~/.codex` 是真实目录,批量 `migrate` 会拒绝覆盖;应使用 `init <name> --migrate-current` 显式迁移当前目录。
91
95
  - `stats` 只以 read-only SQLite URI 读取 `state_*.sqlite`,不写入 Codex 数据库。
92
- - 不实现 `quota`/`refresh` 这类依赖私有接口或私有行为的功能。
96
+ - `stats` 聚合每日、模型、workspace、account 维度;无法推断的 workspace/account/model 显示为 `unknown`。
97
+ - 默认不启用 `quota`/`refresh` 这类依赖私有接口或私有行为的能力;只作为显式开启的实验功能提供。
98
+ - 实验性 `quota` / `accounts list -a` / `accounts refresh` 是显式开启的 private API capability;默认关闭,失败隔离,不参与 workspace/account 切换。
99
+ - private API provider 只在配置中开启且配置 endpoint 或注入 provider 后使用;请求有 timeout,错误会脱敏,输出和 cache 不包含 token/cookie/authorization。
100
+ - quota cache 存放在 `~/.codex-workspaces/cache/quota/`,按 TTL 和 auth_hash 失效,只保存 quota 摘要。
93
101
  - macOS 上迁移前必须确认 Codex App 未运行。
94
102
  - Codex 内置 Terminal 中的危险命令要么转交外部 Terminal,要么拒绝。
95
103
  - 任何切换都只替换 active link,不删除 workspace 目录。
@@ -110,6 +118,15 @@ Windows:
110
118
  - `codex-workspaces accounts add <account> --login`
111
119
  - `codex-workspaces accounts login-temp <account>`
112
120
  - `codex-workspaces accounts cleanup-login-temp`
121
+ - `codex-workspaces accounts refresh-meta <account>|--all [--overwrite]`
122
+ - `codex-workspaces accounts export <backup.tar.gz> [--all|--account <account>] [--include-auth] [--yes]`
123
+ - `codex-workspaces accounts import <backup.tar.gz> [--dry-run] [--rename-conflicts|--overwrite]`
124
+ - `codex-workspaces stats [summary|daily|models|workspaces|accounts] [--format table|json|markdown]`
125
+ - `codex-workspaces config get/set experimental_private_api.*`
126
+ - `codex-workspaces quota [--json] [--no-cache]`
127
+ - `codex-workspaces accounts quota <account> [--json] [--no-cache]`
128
+ - `codex-workspaces accounts list -a [--json] [--no-cache] [--verbose]`
129
+ - `codex-workspaces accounts refresh [account|--all] [--json]`
113
130
  - `codex-workspaces accounts import-workspaces`
114
131
  - `codex-workspaces accounts import-legacy <legacy_accounts_dir>`
115
132
 
@@ -88,9 +88,9 @@ permissions:
88
88
  1. 更新版本号和 `CHANGELOG.md`。
89
89
  2. 本地执行测试和构建检查。
90
90
  3. 合并到 `main`。
91
- 4. 创建 Git tag,例如 `v0.3.1`,触发 `Publish to TestPyPI`。
91
+ 4. 创建 Git tag,例如 `v0.3.3`,触发 `Publish to TestPyPI`。
92
92
  5. 确认 TestPyPI 上传和安装正常。
93
- 6. 从同一个提交创建并推送正式发布分支,例如 `release/v0.3.1`,触发 `Publish to PyPI`。
93
+ 6. 从同一个提交创建并推送正式发布分支,例如 `release/v0.3.3`,触发 `Publish to PyPI`。
94
94
  7. 如果 `pypi` Environment 配置了 Required reviewers,在 GitHub Actions 里批准部署。
95
95
  8. 在 PyPI 页面确认 wheel、sdist 和 README 渲染正常。
96
96
 
@@ -7,9 +7,10 @@
7
7
  - 文件系统行为:初始化统一目录工作区、切换链接、拒绝覆盖真实目录。
8
8
  - CLI 行为:命令别名、工作区名快捷切换、错误路径、帮助输出。
9
9
  - 管理行为:诊断、列表元信息、工作区详情、重命名、删除保护、备注读写和账号绑定。
10
- - 账号行为:账号快照保存、列表/详情增强、备注、重命名、删除保护、临时切换、login-temp 新增账号、默认账号恢复、默认账号设置。
10
+ - 账号行为:账号快照保存、auth 元信息 best-effort 解析、列表/详情增强、备份导出导入、备注、重命名、删除保护、临时切换、login-temp 新增账号、默认账号恢复、默认账号设置。
11
11
  - 迁移行为:旧 `~/.codex-<name>` 工作区迁移、旧 `~/.codex-accounts` 导入、dry-run 不落盘、迁移前备份。
12
- - 统计行为:只读 `state_*.sqlite`,汇总 token、模型、最近会话和每日用量。
12
+ - 统计行为:只读 `state_*.sqlite`,汇总 input/output/total token、模型、最近会话、每日、workspace、account,并覆盖 JSON/Markdown 输出。
13
+ - 实验行为:private API 默认关闭、显式配置、mock provider 成功/失败、quota cache、JSON 输出脱敏、普通切换不触发 private API。
13
14
  - 平台行为:macOS App 控制可注入,非 macOS 自动跳过 App 启停,Codex 内置 Terminal 阻止或转交危险操作。
14
15
 
15
16
  ## 测试结构
@@ -79,6 +80,11 @@ codex-workspaces doctor
79
80
  # CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
80
81
  # CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
81
82
  # codex-workspaces stats personal --days 14
83
+ # codex-workspaces stats summary --days 30
84
+ # codex-workspaces stats daily --format markdown
85
+ # codex-workspaces stats models --format json
86
+ # codex-workspaces stats workspaces
87
+ # codex-workspaces stats accounts
82
88
 
83
89
  printf '{"account":"personal"}\n' > "$tmp_home/.codex-workspaces/workspaces/personal/auth.json"
84
90
 
@@ -98,6 +104,38 @@ CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
98
104
  CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
99
105
  codex-workspaces accounts info personal
100
106
 
107
+ CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
108
+ CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
109
+ codex-workspaces accounts refresh-meta personal
110
+
111
+ CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
112
+ CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
113
+ codex-workspaces accounts export "$tmp_home/accounts-meta.tar.gz" --all
114
+
115
+ # 包含 auth.json 的备份包含凭据,必须安全保存,不能提交到 git。
116
+ CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
117
+ CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
118
+ codex-workspaces accounts export "$tmp_home/accounts-with-auth.tar.gz" --all --include-auth --yes
119
+
120
+ CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
121
+ CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
122
+ codex-workspaces accounts import "$tmp_home/accounts-with-auth.tar.gz" --dry-run
123
+
124
+ # 实验性实时额度功能默认关闭;如需手动验收,需要显式开启。
125
+ CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
126
+ CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
127
+ codex-workspaces config set experimental_private_api.enabled true
128
+
129
+ CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
130
+ CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
131
+ codex-workspaces config set experimental_private_api.quota_enabled true
132
+
133
+ # 需要配置实际 provider endpoint 后才会真实请求;无 endpoint 时会安全失败。
134
+ # CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
135
+ # CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
136
+ # codex-workspaces quota --json
137
+ # codex-workspaces accounts list -a
138
+
101
139
  # 新账号登录流程会切到临时 login-research 工作区,登录完成后恢复原工作区。
102
140
  # CODEX_WORKSPACES_LINK="$tmp_home/.codex" \
103
141
  # CODEX_WORKSPACES_ROOT="$tmp_home/.codex-workspaces" \
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codex-workspaces"
7
- version = "0.3.1"
7
+ version = "0.3.3"
8
8
  description = "Cross-platform Codex workspace switcher with a preserved macOS shell workflow."
9
9
  readme = "README.MD"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """Codex workspace switching utilities."""
2
2
 
3
- __version__ = "0.3.1"
3
+ __version__ = "0.3.3"
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import re
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
12
+ SENSITIVE_KEY_PARTS = (
13
+ "token",
14
+ "secret",
15
+ "credential",
16
+ "authorization",
17
+ "refresh",
18
+ "access",
19
+ "cookie",
20
+ )
21
+
22
+
23
+ @dataclass
24
+ class AuthInspection:
25
+ email: str | None = None
26
+ account_id: str | None = None
27
+ user_id: str | None = None
28
+ organization_id: str | None = None
29
+ plan: str | None = None
30
+ auth_hash: str | None = None
31
+ raw_keys: list[str] = field(default_factory=list)
32
+ warnings: list[str] = field(default_factory=list)
33
+
34
+
35
+ def inspect_auth_file(auth_path: Path) -> AuthInspection:
36
+ result = AuthInspection()
37
+ try:
38
+ result.auth_hash = file_hash(auth_path)
39
+ except OSError as exc:
40
+ result.warnings.append(f"could not read auth file: {exc}")
41
+ return result
42
+
43
+ try:
44
+ data = json.loads(auth_path.read_text(encoding="utf-8"))
45
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
46
+ result.warnings.append(f"could not parse auth JSON: {exc}")
47
+ return result
48
+
49
+ scan_value(data, result)
50
+ return result
51
+
52
+
53
+ def file_hash(path: Path) -> str:
54
+ digest = hashlib.sha256()
55
+ with path.open("rb") as handle:
56
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
57
+ digest.update(chunk)
58
+ return "sha256:" + digest.hexdigest()
59
+
60
+
61
+ def scan_value(value: Any, result: AuthInspection, key_path: tuple[str, ...] = ()) -> None:
62
+ if isinstance(value, dict):
63
+ for key, child in value.items():
64
+ key_text = str(key)
65
+ if is_sensitive_key(key_text):
66
+ continue
67
+ if key_text not in result.raw_keys:
68
+ result.raw_keys.append(key_text)
69
+ scan_key_value(key_text, child, result, key_path)
70
+ scan_value(child, result, (*key_path, key_text))
71
+ elif isinstance(value, list):
72
+ for child in value:
73
+ scan_value(child, result, key_path)
74
+
75
+
76
+ def scan_key_value(key: str, value: Any, result: AuthInspection, key_path: tuple[str, ...]) -> None:
77
+ if not isinstance(value, (str, int)):
78
+ return
79
+ text = str(value).strip()
80
+ if not text or len(text) > 256:
81
+ return
82
+ lower_key = key.lower().replace("-", "_")
83
+ parent_key = key_path[-1].lower().replace("-", "_") if key_path else ""
84
+
85
+ if result.email is None and ("email" in lower_key or EMAIL_RE.match(text)) and EMAIL_RE.match(text):
86
+ result.email = text
87
+ return
88
+
89
+ if result.account_id is None and (is_account_key(lower_key) or (lower_key == "id" and is_account_key(parent_key))) and looks_like_identifier(text):
90
+ result.account_id = text
91
+ if result.user_id is None and (is_user_key(lower_key) or (lower_key == "id" and is_user_key(parent_key))) and looks_like_identifier(text):
92
+ result.user_id = text
93
+ if result.organization_id is None and (is_org_key(lower_key) or (lower_key == "id" and is_org_key(parent_key))) and looks_like_identifier(text):
94
+ result.organization_id = text
95
+ if result.plan is None and is_plan_key(lower_key):
96
+ result.plan = text
97
+
98
+
99
+ def is_sensitive_key(key: str) -> bool:
100
+ lower = key.lower()
101
+ return any(part in lower for part in SENSITIVE_KEY_PARTS)
102
+
103
+
104
+ def is_account_key(key: str) -> bool:
105
+ normalized = key.replace("_", "")
106
+ return normalized in {"accountid", "account"} or "account" in key
107
+
108
+
109
+ def is_user_key(key: str) -> bool:
110
+ normalized = key.replace("_", "")
111
+ return normalized in {"userid", "user", "sub"} or "user" in key
112
+
113
+
114
+ def is_org_key(key: str) -> bool:
115
+ return "organization" in key or key == "org" or key.startswith("org_")
116
+
117
+
118
+ def is_plan_key(key: str) -> bool:
119
+ return "plan" in key or "subscription" in key or "tier" in key
120
+
121
+
122
+ def looks_like_identifier(value: str) -> bool:
123
+ if EMAIL_RE.match(value):
124
+ return False
125
+ return bool(re.match(r"^[A-Za-z0-9._:@-]{1,128}$", value))