codex-manager 3.0.0__tar.gz → 4.0.0__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_manager-4.0.0/PKG-INFO +201 -0
- codex_manager-4.0.0/README.md +167 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/pyproject.toml +2 -2
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/__init__.py +1 -1
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/account_status.py +31 -16
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/args.py +65 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/cli.py +33 -10
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/profile.py +15 -3
- codex_manager-4.0.0/src/codex_manager/purge.py +48 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/registry.py +26 -5
- codex_manager-4.0.0/src/codex_manager/remove.py +106 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/restore.py +1 -1
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/status.py +1 -1
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/ui.py +15 -0
- codex_manager-4.0.0/src/codex_manager.egg-info/PKG-INFO +201 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager.egg-info/SOURCES.txt +21 -1
- codex_manager-4.0.0/tests/test_account_status_coverage.py +141 -0
- codex_manager-4.0.0/tests/test_account_status_more2.py +81 -0
- codex_manager-4.0.0/tests/test_cli_coverage.py +69 -0
- codex_manager-4.0.0/tests/test_cli_coverage_2.py +33 -0
- codex_manager-4.0.0/tests/test_cli_coverage_3.py +42 -0
- codex_manager-4.0.0/tests/test_cli_coverage_4.py +71 -0
- codex_manager-4.0.0/tests/test_cli_coverage_5.py +33 -0
- codex_manager-4.0.0/tests/test_cli_coverage_6.py +30 -0
- codex_manager-4.0.0/tests/test_cli_coverage_7.py +58 -0
- codex_manager-4.0.0/tests/test_cli_coverage_8.py +81 -0
- codex_manager-4.0.0/tests/test_cli_coverage_extra.py +88 -0
- codex_manager-4.0.0/tests/test_cli_coverage_more.py +68 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_cooldown.py +2 -1
- codex_manager-4.0.0/tests/test_dry_run_coverage.py +262 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_list_backups3.py +0 -2
- codex_manager-4.0.0/tests/test_list_backups_coverage.py +83 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_next_gen_upgrade.py +11 -15
- codex_manager-4.0.0/tests/test_purge.py +83 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_recommend.py +2 -1
- codex_manager-4.0.0/tests/test_registry_coverage_more.py +23 -0
- codex_manager-4.0.0/tests/test_registry_dry_run_coverage.py +48 -0
- codex_manager-4.0.0/tests/test_remove.py +69 -0
- codex_manager-3.0.0/CODEX_MANAGER_SPEC.md +0 -49
- codex_manager-3.0.0/PKG-INFO +0 -83
- codex_manager-3.0.0/src/codex_manager.egg-info/PKG-INFO +0 -83
- {codex_manager-3.0.0 → codex_manager-4.0.0}/setup.cfg +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/backup.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/cloud.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/config.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/cooldown.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/credentials.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/doctor.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/list_backups.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/prune.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/prune_backups.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/recommend.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/sync.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/use_account.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager/utils.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager.egg-info/dependency_links.txt +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager.egg-info/entry_points.txt +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager.egg-info/requires.txt +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/src/codex_manager.egg-info/top_level.txt +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_args_cli.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_args_cli_3.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_args_cli_extra.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_backup.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_backup2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_cli2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_cloud.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_config.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_cooldown2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_credentials2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_doctor.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_doctor2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_list_backups.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_list_backups2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_profile.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_prune.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_prune_backups.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_prune_backups2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_recommend2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_restore.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_restore2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_status.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_status2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_status3.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_sync.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_sync2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_ui.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_ui_2.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_use.py +0 -0
- {codex_manager-3.0.0 → codex_manager-4.0.0}/tests/test_use2.py +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codex-manager
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: Codex account snapshot manager
|
|
5
|
+
Author-email: Dhruv <dhruv13x@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dhruv13x/codex-manager
|
|
8
|
+
Project-URL: Repository, https://github.com/dhruv13x/codex-manager
|
|
9
|
+
Project-URL: Issues, https://github.com/dhruv13x/codex-manager/issues
|
|
10
|
+
Keywords: codex,backup,cli,automation
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: rich>=13.0.0
|
|
19
|
+
Requires-Dist: boto3>=1.28.0
|
|
20
|
+
Requires-Dist: b2sdk>=2.0.0
|
|
21
|
+
Requires-Dist: relm>=7.1.1
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-timeout>=2.2.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-json-report>=1.5.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pyfakefs>=5.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=24.3.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.11.0; extra == "dev"
|
|
33
|
+
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
34
|
+
|
|
35
|
+
# 📦 Codex Manager
|
|
36
|
+
|
|
37
|
+
**The ultimate CLI tool for managing OpenAI Codex account snapshots, tracking quotas, and ensuring seamless workflow continuity.**
|
|
38
|
+
|
|
39
|
+
[](https://opensource.org/licenses/MIT)
|
|
40
|
+
[](https://www.python.org/downloads/)
|
|
41
|
+
[](https://github.com/psf/black)
|
|
42
|
+
[](https://github.com/astral-sh/ruff)
|
|
43
|
+
[](#)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## ⚡ Quick Start
|
|
48
|
+
|
|
49
|
+
Get up and running in under 5 minutes.
|
|
50
|
+
|
|
51
|
+
### Prerequisites
|
|
52
|
+
- Python 3.10 or higher
|
|
53
|
+
- `uv` (recommended for fast dependency management)
|
|
54
|
+
|
|
55
|
+
### Install
|
|
56
|
+
Clone the repository and install using `uv`:
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/dhruv13x/codex-manager.git
|
|
59
|
+
cd codex-manager
|
|
60
|
+
uv pip install --system -e .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Run
|
|
64
|
+
Verify the installation by viewing the CLI help menu:
|
|
65
|
+
```bash
|
|
66
|
+
codex-manager --help
|
|
67
|
+
# Or use the shorter alias:
|
|
68
|
+
cm --help
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Demo
|
|
72
|
+
Here's a quick 5-line workflow to backup your current account, view recommendations, and switch to a new one:
|
|
73
|
+
```bash
|
|
74
|
+
# 1. Take a live snapshot of your active Codex account state
|
|
75
|
+
cm backup --cloud
|
|
76
|
+
|
|
77
|
+
# 2. Check cooldown statuses for all accounts
|
|
78
|
+
cm cooldown
|
|
79
|
+
|
|
80
|
+
# 3. Get the smartest recommendation for the next account to use
|
|
81
|
+
cm recommend
|
|
82
|
+
|
|
83
|
+
# 4. Switch to a new account using the 'auth-only' method
|
|
84
|
+
cm use --email new_user@example.com
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
> **[🖼️ Suggestion: Add an animated GIF here demonstrating the `cm use` command in action with the Rich UI]**
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## ✨ Features
|
|
92
|
+
|
|
93
|
+
**Core**
|
|
94
|
+
- **Live Status Tracking:** Automatically parses live Codex `/status` output to capture account email, quota text, and weekly reset timestamps.
|
|
95
|
+
- **Smart Recommendations:** Recommends the optimal account to use next based on calculated cooldowns and real-time metadata.
|
|
96
|
+
- **Full State Recovery:** Backup and restore full Codex runtime states (`auth.json`, history, logs) via `*.tar.gz` archives and `*.metadata.json`.
|
|
97
|
+
|
|
98
|
+
**Performance & Reliability**
|
|
99
|
+
- **Offline & Fallback Mode:** Employs an emergency `--without-status-check` fallback that gracefully estimates cooldowns even when the live tracker is temporarily unavailable.
|
|
100
|
+
- **Lightning Fast UI:** Employs the `rich` library for beautiful terminal output, tables, and status animations.
|
|
101
|
+
|
|
102
|
+
**Security & Cloud**
|
|
103
|
+
- **Cloud Synchronization:** First-class support for Backblaze B2 (and S3-compatible buckets) for remote backup metadata and archive storage.
|
|
104
|
+
- **Safe Operations:** Every modifying command supports a `--dry-run` flag to safely simulate actions without touching your files.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 🛠️ Configuration
|
|
109
|
+
|
|
110
|
+
Codex Manager prioritizes configuration via environment variables and CLI arguments for flexibility.
|
|
111
|
+
|
|
112
|
+
### Environment Variables
|
|
113
|
+
|
|
114
|
+
| Name | Description | Default | Required |
|
|
115
|
+
|---|---|---|---|
|
|
116
|
+
| `CODEX_MANAGER_HOME` | The primary home directory for the manager config and backups. | `~/.codex-manager` | No |
|
|
117
|
+
| `CODEX_HOME` | The target directory where Codex state resides. | `~/.codex` | No |
|
|
118
|
+
|
|
119
|
+
*Note: You can also place a `config.json` inside your `CODEX_MANAGER_HOME` to persist configuration settings.*
|
|
120
|
+
|
|
121
|
+
### Key CLI Arguments
|
|
122
|
+
|
|
123
|
+
Most commands support these primary flags. Use `cm <command> --help` for a complete list.
|
|
124
|
+
|
|
125
|
+
| Flag | Description |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `--dry-run` | Safely preview the changes without modifying local or cloud state. |
|
|
128
|
+
| `--cloud` | Enable Backblaze B2/S3 cloud capabilities for the command. |
|
|
129
|
+
| `--email <email>` | Specify a target account email for use, restore, or listing. |
|
|
130
|
+
| `--backup-dir <dir>` | Override the directory used for reading/writing backups. |
|
|
131
|
+
| `--without-status-check`| Bypass live capture and calculate cooldowns statically (+7 days). |
|
|
132
|
+
| `--auth-only` | During `use` or `backup`, target only identity/auth files instead of the full state. |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 🏗️ Architecture
|
|
137
|
+
|
|
138
|
+
### Directory Tree
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
~/.codex-manager/ (CODEX_MANAGER_HOME)
|
|
142
|
+
├── backups/ # Local archives and metadata
|
|
143
|
+
│ ├── backup_1.tar.gz
|
|
144
|
+
│ └── backup_1.metadata.json
|
|
145
|
+
├── config.json # Persistent local configurations
|
|
146
|
+
└── cooldown.json # Registry caching overall account cooldowns
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### High-Level Data Flow
|
|
150
|
+
1. **Capture:** `cm backup` or `cm status` reads live text from a `tmux` session running Codex.
|
|
151
|
+
2. **Process:** The CLI parses the text to build a state model (Quota, Cooldown, Email) and packages the `~/.codex` directory into an archive.
|
|
152
|
+
3. **Store:** Archives and adjacent JSON metadata are saved locally and pushed to the cloud (if configured).
|
|
153
|
+
4. **Evaluate:** When `cm recommend` or `cm cooldown` is invoked, local and cloud metadata are fetched, evaluated against real-time, and ranked to find the optimal active account.
|
|
154
|
+
5. **Switch:** `cm use` restores the selected account's data into the `~/.codex` home, rotating your session seamlessly.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 🐞 Troubleshooting
|
|
159
|
+
|
|
160
|
+
### Common Issues
|
|
161
|
+
|
|
162
|
+
| Error Message | Cause | Solution |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `TokenExpiredError: TOKEN EXPIRED` | The active Codex session token has expired. | Re-authenticate in Codex manually, or run with `--without-status-check` to bypass. |
|
|
165
|
+
| `Could not resolve Cloud (B2) credentials.` | Missing B2 credentials for cloud sync. | Pass `--b2-id` and `--b2-key` flags, or ensure your credentials are set up. |
|
|
166
|
+
| `No backups found in Cloud for <email>.` | The requested account isn't backed up to the specified bucket. | Run `cm list-backups --cloud` to verify the email and backup availability. |
|
|
167
|
+
|
|
168
|
+
### Debug Mode
|
|
169
|
+
While the CLI does not have a single `--debug` flag, you can often reveal more information by viewing the full exception traces or by utilizing the built-in doctor command:
|
|
170
|
+
```bash
|
|
171
|
+
cm doctor
|
|
172
|
+
```
|
|
173
|
+
The `doctor` command verifies your dependencies, runtime directories, and validates the status parser setup.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 🤝 Contributing
|
|
178
|
+
|
|
179
|
+
We welcome contributions to Codex Manager! Please review our `CONTRIBUTING.md` (coming soon) before submitting PRs.
|
|
180
|
+
|
|
181
|
+
### Dev Setup
|
|
182
|
+
To set up your local development environment:
|
|
183
|
+
```bash
|
|
184
|
+
# 1. Install all development dependencies
|
|
185
|
+
uv pip install --system -e .[dev]
|
|
186
|
+
|
|
187
|
+
# 2. Run the tests (Ensure 90%+ coverage)
|
|
188
|
+
python -m pytest tests --cov=src --cov-report=term-missing
|
|
189
|
+
|
|
190
|
+
# 3. Format and lint the codebase
|
|
191
|
+
uv run ruff check --fix src/ tests/
|
|
192
|
+
uv run black src/ tests/
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🗺️ Roadmap
|
|
198
|
+
|
|
199
|
+
- **Plugin Architecture:** Allow custom plugins to manage other CLI authentication tokens.
|
|
200
|
+
- **Enhanced Cloud Coverage:** Add direct first-class integrations for Google Cloud Storage and Azure Blob.
|
|
201
|
+
- **Automated Rotation Daemon:** A background worker to automatically rotate accounts when token limits are reached in real-time.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# 📦 Codex Manager
|
|
2
|
+
|
|
3
|
+
**The ultimate CLI tool for managing OpenAI Codex account snapshots, tracking quotas, and ensuring seamless workflow continuity.**
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://github.com/psf/black)
|
|
8
|
+
[](https://github.com/astral-sh/ruff)
|
|
9
|
+
[](#)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ⚡ Quick Start
|
|
14
|
+
|
|
15
|
+
Get up and running in under 5 minutes.
|
|
16
|
+
|
|
17
|
+
### Prerequisites
|
|
18
|
+
- Python 3.10 or higher
|
|
19
|
+
- `uv` (recommended for fast dependency management)
|
|
20
|
+
|
|
21
|
+
### Install
|
|
22
|
+
Clone the repository and install using `uv`:
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/dhruv13x/codex-manager.git
|
|
25
|
+
cd codex-manager
|
|
26
|
+
uv pip install --system -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Run
|
|
30
|
+
Verify the installation by viewing the CLI help menu:
|
|
31
|
+
```bash
|
|
32
|
+
codex-manager --help
|
|
33
|
+
# Or use the shorter alias:
|
|
34
|
+
cm --help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Demo
|
|
38
|
+
Here's a quick 5-line workflow to backup your current account, view recommendations, and switch to a new one:
|
|
39
|
+
```bash
|
|
40
|
+
# 1. Take a live snapshot of your active Codex account state
|
|
41
|
+
cm backup --cloud
|
|
42
|
+
|
|
43
|
+
# 2. Check cooldown statuses for all accounts
|
|
44
|
+
cm cooldown
|
|
45
|
+
|
|
46
|
+
# 3. Get the smartest recommendation for the next account to use
|
|
47
|
+
cm recommend
|
|
48
|
+
|
|
49
|
+
# 4. Switch to a new account using the 'auth-only' method
|
|
50
|
+
cm use --email new_user@example.com
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> **[🖼️ Suggestion: Add an animated GIF here demonstrating the `cm use` command in action with the Rich UI]**
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## ✨ Features
|
|
58
|
+
|
|
59
|
+
**Core**
|
|
60
|
+
- **Live Status Tracking:** Automatically parses live Codex `/status` output to capture account email, quota text, and weekly reset timestamps.
|
|
61
|
+
- **Smart Recommendations:** Recommends the optimal account to use next based on calculated cooldowns and real-time metadata.
|
|
62
|
+
- **Full State Recovery:** Backup and restore full Codex runtime states (`auth.json`, history, logs) via `*.tar.gz` archives and `*.metadata.json`.
|
|
63
|
+
|
|
64
|
+
**Performance & Reliability**
|
|
65
|
+
- **Offline & Fallback Mode:** Employs an emergency `--without-status-check` fallback that gracefully estimates cooldowns even when the live tracker is temporarily unavailable.
|
|
66
|
+
- **Lightning Fast UI:** Employs the `rich` library for beautiful terminal output, tables, and status animations.
|
|
67
|
+
|
|
68
|
+
**Security & Cloud**
|
|
69
|
+
- **Cloud Synchronization:** First-class support for Backblaze B2 (and S3-compatible buckets) for remote backup metadata and archive storage.
|
|
70
|
+
- **Safe Operations:** Every modifying command supports a `--dry-run` flag to safely simulate actions without touching your files.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 🛠️ Configuration
|
|
75
|
+
|
|
76
|
+
Codex Manager prioritizes configuration via environment variables and CLI arguments for flexibility.
|
|
77
|
+
|
|
78
|
+
### Environment Variables
|
|
79
|
+
|
|
80
|
+
| Name | Description | Default | Required |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| `CODEX_MANAGER_HOME` | The primary home directory for the manager config and backups. | `~/.codex-manager` | No |
|
|
83
|
+
| `CODEX_HOME` | The target directory where Codex state resides. | `~/.codex` | No |
|
|
84
|
+
|
|
85
|
+
*Note: You can also place a `config.json` inside your `CODEX_MANAGER_HOME` to persist configuration settings.*
|
|
86
|
+
|
|
87
|
+
### Key CLI Arguments
|
|
88
|
+
|
|
89
|
+
Most commands support these primary flags. Use `cm <command> --help` for a complete list.
|
|
90
|
+
|
|
91
|
+
| Flag | Description |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `--dry-run` | Safely preview the changes without modifying local or cloud state. |
|
|
94
|
+
| `--cloud` | Enable Backblaze B2/S3 cloud capabilities for the command. |
|
|
95
|
+
| `--email <email>` | Specify a target account email for use, restore, or listing. |
|
|
96
|
+
| `--backup-dir <dir>` | Override the directory used for reading/writing backups. |
|
|
97
|
+
| `--without-status-check`| Bypass live capture and calculate cooldowns statically (+7 days). |
|
|
98
|
+
| `--auth-only` | During `use` or `backup`, target only identity/auth files instead of the full state. |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 🏗️ Architecture
|
|
103
|
+
|
|
104
|
+
### Directory Tree
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
~/.codex-manager/ (CODEX_MANAGER_HOME)
|
|
108
|
+
├── backups/ # Local archives and metadata
|
|
109
|
+
│ ├── backup_1.tar.gz
|
|
110
|
+
│ └── backup_1.metadata.json
|
|
111
|
+
├── config.json # Persistent local configurations
|
|
112
|
+
└── cooldown.json # Registry caching overall account cooldowns
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### High-Level Data Flow
|
|
116
|
+
1. **Capture:** `cm backup` or `cm status` reads live text from a `tmux` session running Codex.
|
|
117
|
+
2. **Process:** The CLI parses the text to build a state model (Quota, Cooldown, Email) and packages the `~/.codex` directory into an archive.
|
|
118
|
+
3. **Store:** Archives and adjacent JSON metadata are saved locally and pushed to the cloud (if configured).
|
|
119
|
+
4. **Evaluate:** When `cm recommend` or `cm cooldown` is invoked, local and cloud metadata are fetched, evaluated against real-time, and ranked to find the optimal active account.
|
|
120
|
+
5. **Switch:** `cm use` restores the selected account's data into the `~/.codex` home, rotating your session seamlessly.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🐞 Troubleshooting
|
|
125
|
+
|
|
126
|
+
### Common Issues
|
|
127
|
+
|
|
128
|
+
| Error Message | Cause | Solution |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `TokenExpiredError: TOKEN EXPIRED` | The active Codex session token has expired. | Re-authenticate in Codex manually, or run with `--without-status-check` to bypass. |
|
|
131
|
+
| `Could not resolve Cloud (B2) credentials.` | Missing B2 credentials for cloud sync. | Pass `--b2-id` and `--b2-key` flags, or ensure your credentials are set up. |
|
|
132
|
+
| `No backups found in Cloud for <email>.` | The requested account isn't backed up to the specified bucket. | Run `cm list-backups --cloud` to verify the email and backup availability. |
|
|
133
|
+
|
|
134
|
+
### Debug Mode
|
|
135
|
+
While the CLI does not have a single `--debug` flag, you can often reveal more information by viewing the full exception traces or by utilizing the built-in doctor command:
|
|
136
|
+
```bash
|
|
137
|
+
cm doctor
|
|
138
|
+
```
|
|
139
|
+
The `doctor` command verifies your dependencies, runtime directories, and validates the status parser setup.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 🤝 Contributing
|
|
144
|
+
|
|
145
|
+
We welcome contributions to Codex Manager! Please review our `CONTRIBUTING.md` (coming soon) before submitting PRs.
|
|
146
|
+
|
|
147
|
+
### Dev Setup
|
|
148
|
+
To set up your local development environment:
|
|
149
|
+
```bash
|
|
150
|
+
# 1. Install all development dependencies
|
|
151
|
+
uv pip install --system -e .[dev]
|
|
152
|
+
|
|
153
|
+
# 2. Run the tests (Ensure 90%+ coverage)
|
|
154
|
+
python -m pytest tests --cov=src --cov-report=term-missing
|
|
155
|
+
|
|
156
|
+
# 3. Format and lint the codebase
|
|
157
|
+
uv run ruff check --fix src/ tests/
|
|
158
|
+
uv run black src/ tests/
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 🗺️ Roadmap
|
|
164
|
+
|
|
165
|
+
- **Plugin Architecture:** Allow custom plugins to manage other CLI authentication tokens.
|
|
166
|
+
- **Enhanced Cloud Coverage:** Add direct first-class integrations for Google Cloud Storage and Azure Blob.
|
|
167
|
+
- **Automated Rotation Daemon:** A background worker to automatically rotate accounts when token limits are reached in real-time.
|
|
@@ -9,9 +9,8 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
from .backup import read_status_text_from_args
|
|
11
11
|
from .cloud import get_cloud_provider
|
|
12
|
-
from .list_backups import
|
|
12
|
+
from .list_backups import list_cloud_backups
|
|
13
13
|
from .registry import sync_registry_with_cloud, update_registry_entry
|
|
14
|
-
from .status import parse_live_status_text
|
|
15
14
|
from .ui import console
|
|
16
15
|
from .utils import build_archive_name
|
|
17
16
|
|
|
@@ -24,6 +23,7 @@ def patch_metadata(
|
|
|
24
23
|
args: Any = None,
|
|
25
24
|
session_start_at: Any | None = None,
|
|
26
25
|
is_expired: bool = False,
|
|
26
|
+
dry_run: bool = False,
|
|
27
27
|
) -> None:
|
|
28
28
|
backup_dir = Path(args.backup_dir).expanduser() if args and hasattr(args, "backup_dir") else Path("~/.codex-manager/backups").expanduser()
|
|
29
29
|
|
|
@@ -78,10 +78,13 @@ def patch_metadata(
|
|
|
78
78
|
data["quota_percent_left"] = quota_percent_left
|
|
79
79
|
data["is_expired"] = is_expired
|
|
80
80
|
data["updated_at"] = datetime.now().astimezone().isoformat()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
if not dry_run:
|
|
82
|
+
metadata_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
83
|
+
console.print(
|
|
84
|
+
f"Updated local metadata for [cyan]{email}[/]: [dim]{metadata_path.name}[/]"
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
console.print(f"Would update local metadata for [cyan]{email}[/]: [dim]{metadata_path.name}[/]")
|
|
85
88
|
except Exception as exc:
|
|
86
89
|
console.print(f"[yellow]Warning:[/] Failed to patch local metadata: {exc}")
|
|
87
90
|
else:
|
|
@@ -113,10 +116,13 @@ def patch_metadata(
|
|
|
113
116
|
"metadata_only": True,
|
|
114
117
|
}
|
|
115
118
|
try:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
if not dry_run:
|
|
120
|
+
metadata_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
121
|
+
console.print(
|
|
122
|
+
f"Created cooldown-only metadata for [cyan]{email}[/]: [dim]{metadata_path.name}[/]"
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
console.print(f"Would create cooldown-only metadata for [cyan]{email}[/]: [dim]{metadata_path.name}[/]")
|
|
120
126
|
except Exception as exc:
|
|
121
127
|
console.print(f"[yellow]Warning:[/] Failed to create local metadata: {exc}")
|
|
122
128
|
|
|
@@ -127,12 +133,13 @@ def patch_metadata(
|
|
|
127
133
|
quota_text=quota_text,
|
|
128
134
|
quota_percent_left=quota_percent_left,
|
|
129
135
|
session_start_at=final_session_start_at,
|
|
136
|
+
dry_run=dry_run,
|
|
130
137
|
)
|
|
131
138
|
|
|
132
139
|
if args and getattr(args, "cloud", False):
|
|
133
140
|
cp = get_cloud_provider(args)
|
|
134
141
|
if cp:
|
|
135
|
-
sync_registry_with_cloud(cp)
|
|
142
|
+
sync_registry_with_cloud(cp, dry_run=dry_run)
|
|
136
143
|
entries = list_cloud_backups(cp, email=email, latest_per_email=True)
|
|
137
144
|
if entries:
|
|
138
145
|
selected = entries[0]
|
|
@@ -161,11 +168,14 @@ def patch_metadata(
|
|
|
161
168
|
data["updated_at"] = datetime.now().astimezone().isoformat()
|
|
162
169
|
local_metadata.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
163
170
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
if not dry_run:
|
|
172
|
+
console.print(
|
|
173
|
+
f"Uploading updated metadata to Cloud: [dim]{metadata_name}[/] ..."
|
|
174
|
+
)
|
|
175
|
+
cp.upload_file(local_metadata, metadata_name)
|
|
176
|
+
console.print("Cloud metadata update complete.")
|
|
177
|
+
else:
|
|
178
|
+
console.print(f"Would upload updated metadata to Cloud: [dim]{metadata_name}[/]")
|
|
169
179
|
except Exception as exc:
|
|
170
180
|
console.print(f"[yellow]Warning:[/] Failed to patch cloud metadata: {exc}")
|
|
171
181
|
else:
|
|
@@ -215,6 +225,7 @@ def sync_current_account_status(args: Any) -> None:
|
|
|
215
225
|
quota_percent_left=None,
|
|
216
226
|
args=args,
|
|
217
227
|
session_start_at=session_start_at,
|
|
228
|
+
dry_run=getattr(args, "dry_run", False),
|
|
218
229
|
)
|
|
219
230
|
return
|
|
220
231
|
|
|
@@ -241,6 +252,7 @@ def sync_current_account_status(args: Any) -> None:
|
|
|
241
252
|
args=args,
|
|
242
253
|
session_start_at=None,
|
|
243
254
|
is_expired=True,
|
|
255
|
+
dry_run=getattr(args, "dry_run", False),
|
|
244
256
|
)
|
|
245
257
|
except Exception:
|
|
246
258
|
# If we can't even get the email, we use the one from auth.json
|
|
@@ -251,6 +263,7 @@ def sync_current_account_status(args: Any) -> None:
|
|
|
251
263
|
quota_percent_left=None,
|
|
252
264
|
args=args,
|
|
253
265
|
is_expired=True,
|
|
266
|
+
dry_run=getattr(args, "dry_run", False),
|
|
254
267
|
)
|
|
255
268
|
sys.exit(1)
|
|
256
269
|
except Exception as exc:
|
|
@@ -268,6 +281,7 @@ def sync_current_account_status(args: Any) -> None:
|
|
|
268
281
|
|
|
269
282
|
if text:
|
|
270
283
|
try:
|
|
284
|
+
from .status import parse_live_status_text
|
|
271
285
|
status = parse_live_status_text(
|
|
272
286
|
text,
|
|
273
287
|
reference_year=getattr(args, "reference_year", None),
|
|
@@ -280,6 +294,7 @@ def sync_current_account_status(args: Any) -> None:
|
|
|
280
294
|
args=args,
|
|
281
295
|
session_start_at=status.session_start_at,
|
|
282
296
|
is_expired=status.is_expired,
|
|
297
|
+
dry_run=getattr(args, "dry_run", False),
|
|
283
298
|
)
|
|
284
299
|
except Exception as exc:
|
|
285
300
|
console.print(
|
|
@@ -565,6 +565,11 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
565
565
|
"file",
|
|
566
566
|
help="Path to the profile archive (.tar.gz) to export to or import from.",
|
|
567
567
|
)
|
|
568
|
+
profile_parser.add_argument(
|
|
569
|
+
"--dry-run",
|
|
570
|
+
action="store_true",
|
|
571
|
+
help="Show what would happen without modifying metadata.",
|
|
572
|
+
)
|
|
568
573
|
|
|
569
574
|
doctor_parser = subparsers.add_parser(
|
|
570
575
|
"doctor",
|
|
@@ -689,6 +694,11 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
689
694
|
use_parser.add_argument("--bucket", help="B2 Bucket Name")
|
|
690
695
|
use_parser.add_argument("--b2-id", help="B2 Key ID")
|
|
691
696
|
use_parser.add_argument("--b2-key", help="B2 App Key")
|
|
697
|
+
status_parser.add_argument(
|
|
698
|
+
"--dry-run",
|
|
699
|
+
action="store_true",
|
|
700
|
+
help="Show what would happen without modifying metadata.",
|
|
701
|
+
)
|
|
692
702
|
|
|
693
703
|
sync_parser = subparsers.add_parser(
|
|
694
704
|
"sync",
|
|
@@ -727,4 +737,59 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
727
737
|
help="Show what would happen without actually syncing.",
|
|
728
738
|
)
|
|
729
739
|
|
|
740
|
+
purge_parser = subparsers.add_parser(
|
|
741
|
+
"purge",
|
|
742
|
+
help="Total Wipeout: Completely delete the Codex home directory (Auth, Identity, and all State).",
|
|
743
|
+
)
|
|
744
|
+
purge_parser.add_argument(
|
|
745
|
+
"--source-dir",
|
|
746
|
+
default=str(_get_default("codex_home", str(DEFAULT_CODEX_HOME))),
|
|
747
|
+
help="Codex home directory to purge.",
|
|
748
|
+
)
|
|
749
|
+
purge_parser.add_argument(
|
|
750
|
+
"--dry-run",
|
|
751
|
+
action="store_true",
|
|
752
|
+
help="Show what would be removed without deleting anything.",
|
|
753
|
+
)
|
|
754
|
+
purge_parser.add_argument(
|
|
755
|
+
"--yes",
|
|
756
|
+
"-y",
|
|
757
|
+
action="store_true",
|
|
758
|
+
help="Confirm the purge without prompting.",
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
remove_parser = subparsers.add_parser(
|
|
762
|
+
"remove",
|
|
763
|
+
help="Account Cleanup: Delete all local (and optionally cloud) backups and registry entries for a specific email.",
|
|
764
|
+
)
|
|
765
|
+
remove_parser.add_argument(
|
|
766
|
+
"--email",
|
|
767
|
+
required=True,
|
|
768
|
+
help="The email address of the account to remove.",
|
|
769
|
+
)
|
|
770
|
+
remove_parser.add_argument(
|
|
771
|
+
"--backup-dir",
|
|
772
|
+
default=str(_get_default("backup_dir", str(DEFAULT_BACKUP_DIR))),
|
|
773
|
+
help="Directory containing backup archives and metadata.",
|
|
774
|
+
)
|
|
775
|
+
remove_parser.add_argument(
|
|
776
|
+
"--cloud",
|
|
777
|
+
action="store_true",
|
|
778
|
+
help="Also remove backups and registry entries from Cloud (B2).",
|
|
779
|
+
)
|
|
780
|
+
remove_parser.add_argument(
|
|
781
|
+
"--dry-run",
|
|
782
|
+
action="store_true",
|
|
783
|
+
help="Show what would be removed without deleting anything.",
|
|
784
|
+
)
|
|
785
|
+
remove_parser.add_argument(
|
|
786
|
+
"--yes",
|
|
787
|
+
"-y",
|
|
788
|
+
action="store_true",
|
|
789
|
+
help="Confirm the removal without prompting.",
|
|
790
|
+
)
|
|
791
|
+
remove_parser.add_argument("--bucket", help="B2 Bucket Name")
|
|
792
|
+
remove_parser.add_argument("--b2-id", help="B2 Key ID")
|
|
793
|
+
remove_parser.add_argument("--b2-key", help="B2 App Key")
|
|
794
|
+
|
|
730
795
|
return parser
|