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.
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/CHANGELOG.md +35 -0
- {codex_workspaces-0.3.1/src/codex_workspaces.egg-info → codex_workspaces-0.3.3}/PKG-INFO +70 -2
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/README.MD +69 -1
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/README.zh-CN.md +69 -1
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/docs/DESIGN.zh-CN.md +18 -1
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/docs/RELEASE.zh-CN.md +2 -2
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/docs/TESTING.zh-CN.md +40 -2
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/pyproject.toml +1 -1
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/__init__.py +1 -1
- codex_workspaces-0.3.3/src/codex_workspaces/auth_inspector.py +125 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/cli.py +156 -4
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/config.py +3 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/core.py +1024 -17
- codex_workspaces-0.3.3/src/codex_workspaces/private_api/__init__.py +10 -0
- codex_workspaces-0.3.3/src/codex_workspaces/private_api/auth.py +45 -0
- codex_workspaces-0.3.3/src/codex_workspaces/private_api/client.py +150 -0
- codex_workspaces-0.3.3/src/codex_workspaces/private_api/errors.py +52 -0
- codex_workspaces-0.3.3/src/codex_workspaces/private_api/models.py +95 -0
- codex_workspaces-0.3.3/src/codex_workspaces/stats.py +311 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/store.py +53 -2
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3/src/codex_workspaces.egg-info}/PKG-INFO +70 -2
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/SOURCES.txt +6 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/tests/test_cli.py +59 -4
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/tests/test_core.py +426 -3
- codex_workspaces-0.3.1/src/codex_workspaces/stats.py +0 -170
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/MANIFEST.in +0 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/setup.cfg +0 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/__main__.py +0 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/errors.py +0 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces/platforms.py +0 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/dependency_links.txt +0 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/entry_points.txt +0 -0
- {codex_workspaces-0.3.1 → codex_workspaces-0.3.3}/src/codex_workspaces.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
|
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
|
-
-
|
|
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.
|
|
91
|
+
4. 创建 Git tag,例如 `v0.3.3`,触发 `Publish to TestPyPI`。
|
|
92
92
|
5. 确认 TestPyPI 上传和安装正常。
|
|
93
|
-
6. 从同一个提交创建并推送正式发布分支,例如 `release/v0.3.
|
|
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
|
-
-
|
|
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.
|
|
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"
|
|
@@ -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))
|