codex-mode 0.1.0a7__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_mode-0.1.0a7/CHANGELOG.md +8 -0
- codex_mode-0.1.0a7/LICENSE +21 -0
- codex_mode-0.1.0a7/MANIFEST.in +5 -0
- codex_mode-0.1.0a7/PKG-INFO +176 -0
- codex_mode-0.1.0a7/PUBLISH.md +46 -0
- codex_mode-0.1.0a7/README.md +160 -0
- codex_mode-0.1.0a7/RELEASE.md +23 -0
- codex_mode-0.1.0a7/SHARE.md +48 -0
- codex_mode-0.1.0a7/pyproject.toml +29 -0
- codex_mode-0.1.0a7/setup.cfg +4 -0
- codex_mode-0.1.0a7/src/codex_mode/__init__.py +2 -0
- codex_mode-0.1.0a7/src/codex_mode/__main__.py +3 -0
- codex_mode-0.1.0a7/src/codex_mode/accounts.py +91 -0
- codex_mode-0.1.0a7/src/codex_mode/benchmark.py +99 -0
- codex_mode-0.1.0a7/src/codex_mode/cli.py +199 -0
- codex_mode-0.1.0a7/src/codex_mode/engine.py +487 -0
- codex_mode-0.1.0a7/src/codex_mode/onboarding.py +583 -0
- codex_mode-0.1.0a7/src/codex_mode.egg-info/PKG-INFO +176 -0
- codex_mode-0.1.0a7/src/codex_mode.egg-info/SOURCES.txt +29 -0
- codex_mode-0.1.0a7/src/codex_mode.egg-info/dependency_links.txt +1 -0
- codex_mode-0.1.0a7/src/codex_mode.egg-info/entry_points.txt +2 -0
- codex_mode-0.1.0a7/src/codex_mode.egg-info/requires.txt +1 -0
- codex_mode-0.1.0a7/src/codex_mode.egg-info/top_level.txt +1 -0
- codex_mode-0.1.0a7/tests/integration_smoke.py +78 -0
- codex_mode-0.1.0a7/tests/nr151_isolated.py +53 -0
- codex_mode-0.1.0a7/tests/test_accounts.py +72 -0
- codex_mode-0.1.0a7/tests/test_benchmark.py +49 -0
- codex_mode-0.1.0a7/tests/test_engine.py +260 -0
- codex_mode-0.1.0a7/tests/test_onboarding.py +284 -0
- codex_mode-0.1.0a7/tests/test_portable.py +107 -0
- codex_mode-0.1.0a7/tests/test_speed_cli.py +14 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# 0.1.0a7
|
|
2
|
+
|
|
3
|
+
- Named ChatGPT accounts, isolated device login, credential refresh preservation,
|
|
4
|
+
identity mismatch refusal and rollback. Existing unnamed commands remain valid.
|
|
5
|
+
- `speed`: isolated hello requests, equal model/effort, alternating sample order,
|
|
6
|
+
successful-sample median, bounded timeout and private sanitized diagnostics.
|
|
7
|
+
- No automatic routing, task interruption, service restart or production migration.
|
|
8
|
+
- Linux alpha; Codex 0.153.4 / 0.154.0 only. MIT; PyPI name ownership pending.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 henrychi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codex-mode
|
|
3
|
+
Version: 0.1.0a7
|
|
4
|
+
Summary: Independent Linux tool to switch Codex ChatGPT and API routes with shared history
|
|
5
|
+
Author: henrychi
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: codex,cli,authentication,benchmark
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: tomlkit<1,>=0.13
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# codex-mode (Linux alpha)
|
|
18
|
+
|
|
19
|
+
独立的 Codex 鉴权切换工具,不是 OpenAI 官方产品。让 ChatGPT 登录与
|
|
20
|
+
Responses API 路由使用同一个 CODEX_HOME,保留聊天历史。
|
|
21
|
+
|
|
22
|
+
当前版本 `0.1.0a7` 是 PyPI 候选发布包,尚未上传。Linux / Python 3.11+,
|
|
23
|
+
已验证 Codex 0.153.4 / 0.154.0;不是 OpenAI 官方产品。
|
|
24
|
+
|
|
25
|
+
新增命名 ChatGPT 账号和隔离测速:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
codex-mode login --account second # 保存登录,不立即启用
|
|
29
|
+
codex-mode accounts
|
|
30
|
+
codex-mode account-save --account first # 服务停止后,保存当前登录
|
|
31
|
+
codex-mode chatgpt --account second # 停止任务、暂停 goal、断开 Desktop 后切换
|
|
32
|
+
codex-mode speed # hello,gpt-5.6-sol / medium,各一次
|
|
33
|
+
codex-mode speed --rounds 3 --timeout 60
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
测速消耗额度/费用,使用空白临时目录,不发送旧聊天或代码,不切换当前线路。
|
|
37
|
+
结果为端到端完整回复耗时,包含 CLI 启动、排队和内部重试,不是首 token 延迟。
|
|
38
|
+
多轮交替顺序,并汇总成功次数和成功样本中位数;失败样本不算成功。
|
|
39
|
+
上游实际模型映射无法由本工具保证,hello 结果也不代表长任务吞吐。
|
|
40
|
+
ChatGPT 测试临时凭据可能触发令牌刷新;首次建议避开重要运行任务。
|
|
41
|
+
命名账号共享本地历史,不能同步云端任务,也不支持同目录双账号并发。
|
|
42
|
+
Python >= 3.11;仅支持 Linux。版本检查目前只接受 Codex 0.153.4 / 0.154.0;
|
|
43
|
+
实验接口可能变化,其他版本需要先完成兼容验证。
|
|
44
|
+
|
|
45
|
+
## 本地安装
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python3 -m venv .venv
|
|
49
|
+
.venv/bin/python -m pip install .
|
|
50
|
+
.venv/bin/codex-mode --version
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
不要覆盖已有稳定版的命令;先在独立虚拟环境验证。包名 `codex-mode` 暂定,
|
|
54
|
+
PyPI 名称可用性、许可证、作者信息和发布账户尚待确认。
|
|
55
|
+
|
|
56
|
+
## 推荐:逐步配置向导
|
|
57
|
+
|
|
58
|
+
先安装 Codex 并运行一次,确保 CODEX_HOME 已有 config.toml。手动停止所有任务、
|
|
59
|
+
暂停 goal,断开该机器的 Desktop;在服务器终端而不是运行中的 Codex 聊天执行:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
codex-mode setup
|
|
63
|
+
# 不带参数也启动向导;指定其他目录:
|
|
64
|
+
codex-mode setup --state-dir /absolute/path/to/codex-home
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
六步:检查环境 → 完整备份 → 扫描历史 provider → 隐藏输入 API key 和模型名
|
|
68
|
+
→ 复用或完成官方 ChatGPT 设备登录 → 隔离验证两种模式。逐步确认是否托管历史
|
|
69
|
+
provider、是否发送真实测试请求、是否最终启用路由。默认不激活路由。
|
|
70
|
+
旧任务只读取,不 resume、不启动 turn、不操作 goal。真实请求仅使用新建临时
|
|
71
|
+
任务和固定一句话,不发送旧聊天。跳过真实请求时不会宣称实际线路可用。
|
|
72
|
+
测试配置关闭 shell、unified exec 和 web search,移除生产 MCP/hooks 等设置。
|
|
73
|
+
参考官方 [鉴权](https://learn.chatgpt.com/docs/auth)、
|
|
74
|
+
[App Server](https://learn.chatgpt.com/docs/app-server) 和
|
|
75
|
+
[配置字段](https://learn.chatgpt.com/docs/config-file/config-reference)。
|
|
76
|
+
|
|
77
|
+
完整备份位于父目录/.codex-mode-backups/<目录名>-<时间>/home,含凭据、聊天、
|
|
78
|
+
数据库和插件,可能占用数十 GB,不要分享。目录 0700、普通文件 0600;SQLite
|
|
79
|
+
backup API 合并已提交 WAL。运行时 socket 不复制,符号链接不跟随,异常及原始
|
|
80
|
+
权限记录在 manifest.json。正式向导拒绝运行中的服务;测试 live capture 不是
|
|
81
|
+
跨文件一致性快照。验证只写元数据/结果,不在报告中写正文和 token。
|
|
82
|
+
|
|
83
|
+
逐个检查全部任务摘要,检查置顶任务和少量样本的最新轮次内容;不是逐字校验全部
|
|
84
|
+
历史,也不能保证 Desktop 每个筛选都显示所有任务。内置 openai 不覆盖,历史
|
|
85
|
+
provider/source/cwd 筛选仍可能隐藏任务,需按 ID 或相应筛选打开。缺失代码目录
|
|
86
|
+
会提示,工具不迁移代码。失败不启用新路由;工具配置/凭据会保留供排查,原主配置
|
|
87
|
+
和鉴权保持不变。
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
codex-mode scan
|
|
91
|
+
codex-mode restore --backup /absolute/path/to/backup-directory
|
|
92
|
+
codex-mode verify --backup /absolute/path/to/backup-directory
|
|
93
|
+
# 已有完整备份时重试验证,无需再次备份;加 --request-test 确认后测试真实请求
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
restore 需停止服务并确认,只还原配置和登录,先保存当前控制文件的撤销副本;不
|
|
97
|
+
覆盖聊天、数据库或代码。旧登录可能需重新授权。备份中不存在的控制文件会移除,
|
|
98
|
+
可从撤销副本找回。只分享 wheel 和 SHARE.md,不分享备份或真实 CODEX_HOME。
|
|
99
|
+
|
|
100
|
+
## 初始化与使用
|
|
101
|
+
|
|
102
|
+
先安装、初始化 Codex,确保选定 CODEX_HOME 中有 config.toml。
|
|
103
|
+
默认使用环境变量 CODEX_HOME,否则使用当前用户的 ~/.codex。
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# API 地址需要指向支持 Responses 的服务,不要在 URL 中放密钥。
|
|
107
|
+
codex-mode init --api-url https://api.example.com/v1 --prompt-key
|
|
108
|
+
# 或使用环境变量(该变量也必须对 Desktop 远程服务可见)
|
|
109
|
+
codex-mode init --api-url https://api.example.com/v1 --key-env MY_CODEX_API_KEY
|
|
110
|
+
|
|
111
|
+
codex-mode doctor
|
|
112
|
+
codex-mode login
|
|
113
|
+
codex-mode chatgpt --dry-run
|
|
114
|
+
codex-mode chatgpt
|
|
115
|
+
codex-mode api
|
|
116
|
+
codex-mode status
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`sub2api` 是 `api` 的兼容命令。`--state-dir DIR` 指定独立目录;
|
|
120
|
+
`--codex-bin PATH` 覆盖 Codex 可执行文件;`init --codex-bin PATH` 将其保存。
|
|
121
|
+
`init --import-api-key` 显式导入现有 auth.json 的 API key,不改现有鉴权。
|
|
122
|
+
可选 `init --health-url URL` 启用独立健康检查,默认不假定服务有 /health。
|
|
123
|
+
|
|
124
|
+
工具配置保存在 CODEX_HOME/codex-mode.json。隐藏输入的 API key 保存在
|
|
125
|
+
CODEX_HOME/auth-profiles/sub2api.auth.json(0600);不是加密保险库。
|
|
126
|
+
也可使用 key_env,不保存 key 值。`_key` 是凭据读取专用内部命令,不应手动调用。
|
|
127
|
+
安装路径及对应虚拟环境必须在 Codex 使用期间保持可访问,生成的凭据命令
|
|
128
|
+
使用该 Python 的绝对路径和 `-m codex_mode`。
|
|
129
|
+
|
|
130
|
+
## 中断与历史边界
|
|
131
|
+
|
|
132
|
+
普通切换前,请手动停止正在运行的任务、暂停 goal,并断开 Desktop。
|
|
133
|
+
工具只复查已加载会话是否空闲,不查询 goal,也不自动暂停或中断任务。
|
|
134
|
+
空闲服务使用 SIGTERM 请求退出,最多等待 30 秒;无法确认空闲时拒绝切换。
|
|
135
|
+
空闲 Desktop 代理仍在时,可用 --disconnect-idle;无法正常停止时用 --force。
|
|
136
|
+
显式 `codex-mode chatgpt --force`(或 `api --force`)绕过会话查询,要求终端确认,
|
|
137
|
+
先向当前用户/当前 CODEX_HOME 的既有服务与代理发送 SIGTERM,5 秒后仍未退出才
|
|
138
|
+
发送 SIGKILL。需要 Python/kernel 的 Linux pidfd 支持;不会终止新出现的连接。
|
|
139
|
+
force 模式不能保证未完成轮次完整保存,也不会暂停或清除 goal;外部后台进程可能
|
|
140
|
+
继续运行。切换完成后重连 Desktop,启动新配置服务。force 仅适合正常切换失败时使用。
|
|
141
|
+
未手动暂停的 goal 可能在重连后继续运行;工具不会改变 goal 状态。
|
|
142
|
+
已中断的轮次不会自动继续,已落盘代码修改不会撤销。
|
|
143
|
+
切换期间不要重新连接 Desktop 或提交新任务;外部客户端不受本工具锁约束,
|
|
144
|
+
状态复查只能缩小并发窗口,不能提供跨客户端事务保证。
|
|
145
|
+
|
|
146
|
+
重新连接 Desktop,在同一聊天检查进度并继续。训练、下载、nohup/systemd 等
|
|
147
|
+
外部进程不受会话中断统一管理,重新运行命令前检查它们。
|
|
148
|
+
|
|
149
|
+
默认只管理稳定 provider ID `direct`。历史 provider ID 不会自动统一,
|
|
150
|
+
CLI 列表可能仍按 provider 过滤;`resume --all` 只取消目录筛选。
|
|
151
|
+
使用 `init --provider-alias OpenAI --provider-alias sub2api` 显式允许工具在切换时
|
|
152
|
+
重写这些 provider 的配置,使它们跟随所选路由。它不会改历史 JSONL 或数据库。
|
|
153
|
+
这些别名原有的专有设置将被替换;其他 providers、项目设置、注释和多行值保留。
|
|
154
|
+
新配置使用文件凭据存储,替代 forced_login_method;不保留 keyring-only 设置。
|
|
155
|
+
|
|
156
|
+
默认日志精简,TTY 支持颜色,NO_COLOR / TERM=dumb 禁用颜色;
|
|
157
|
+
`--verbose` 查看任务 ID、备份路径等。`--dry-run` 仅验证配置转换,不验证网络、
|
|
158
|
+
任务状态或实际模型请求。
|
|
159
|
+
|
|
160
|
+
## 网络与发布
|
|
161
|
+
|
|
162
|
+
不自动配置 SSH、系统路由或中继。已有 SSH 转发可作为 api-url 指向的本地端口,
|
|
163
|
+
由用户另外管理。
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
python -m unittest discover -s tests -v
|
|
167
|
+
# 实际 Codex + 临时 CODEX_HOME + loopback 模拟 API,不使用真实凭据:
|
|
168
|
+
python tests/integration_smoke.py /absolute/path/to/codex
|
|
169
|
+
python -m build
|
|
170
|
+
python -m twine check dist/*
|
|
171
|
+
# 先确认名称/许可证/账号,再手动上传 TestPyPI;不要上传凭据或历史。
|
|
172
|
+
# python -m twine upload --repository testpypi dist/*
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
发布前待办见 RELEASE.md。安装不会读取或修改实际 Codex 登录;init 只更改
|
|
176
|
+
工具自己的配置/凭据,mode 命令才执行切换并备份 config.toml/auth.json。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 发布与迁移
|
|
2
|
+
|
|
3
|
+
当前候选版本 0.1.0a7,MIT,署名 henrychi,尚未上传。公开前确认包名及账号权限。
|
|
4
|
+
|
|
5
|
+
## 发布(维护者)
|
|
6
|
+
|
|
7
|
+
1. 注册 PyPI 和 TestPyPI(两个站点账号独立),验证邮箱,开启 2FA。
|
|
8
|
+
2. 确认包名可用/属于自己;页面不存在不保证包名可注册。
|
|
9
|
+
3. 本版已写入 MIT 许可证与署名;修改后必须重新构建、测试。
|
|
10
|
+
4. 在发布虚拟环境执行:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
python -m build --outdir release/0.1.0a7
|
|
14
|
+
python -m twine check release/0.1.0a7/*
|
|
15
|
+
python -m twine upload --repository testpypi release/0.1.0a7/*
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
上传使用站点 API token:用户名 `__token__`,密码提示中粘贴 token。
|
|
19
|
+
不要把 token 写入命令、代码或发到聊天。正式 PyPI 和 TestPyPI token 不通用。
|
|
20
|
+
未来可用 Trusted Publishing 避免长期 token。
|
|
21
|
+
|
|
22
|
+
在新虚拟环境先从正式 PyPI 安装依赖 tomlkit,再仅从 TestPyPI 安装候选包:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python -m pip install 'tomlkit>=0.13,<1'
|
|
26
|
+
python -m pip install --index-url https://test.pypi.org/simple/ --no-deps codex-mode==0.1.0a7
|
|
27
|
+
codex-mode --version
|
|
28
|
+
codex-mode --help
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
完成隔离验证后,再执行 `python -m twine upload release/0.1.0a7/*`。
|
|
32
|
+
只上传此版本目录,不上传 dist 中旧包或分享 ZIP。已上传文件名不能重用;
|
|
33
|
+
修复后递增版本。alpha 版安装需指定版本或使用 --pre。
|
|
34
|
+
|
|
35
|
+
## NR151 迁移(任务停止后,由我们处理)
|
|
36
|
+
|
|
37
|
+
无需删除 CODEX_HOME 或聊天。保留现用脚本及入口作为回滚备份。
|
|
38
|
+
先独立安装 wheel/PyPI 包到新虚拟环境,确认入口、版本和隔离测试通过,
|
|
39
|
+
再原子替换 codex-mode 入口;不修改 Codex 本身的安装。
|
|
40
|
+
用 init 显式登记原有 API 地址、中继健康地址、Codex 路径、历史 provider 别名。
|
|
41
|
+
现有 API key 文件可继续读取,无需重新输入或分享。
|
|
42
|
+
迁移时只备份并调整工具配置;不得覆盖聊天数据库、账号登录或启动旧任务。
|
|
43
|
+
当前 stable 脚本 1.5.0 与 package 0.1.0a7 是两套版本号,功能统一后只维护包。
|
|
44
|
+
|
|
45
|
+
参考:https://packaging.python.org/en/latest/tutorials/packaging-projects/
|
|
46
|
+
PyPI:https://pypi.org/help/
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# codex-mode (Linux alpha)
|
|
2
|
+
|
|
3
|
+
独立的 Codex 鉴权切换工具,不是 OpenAI 官方产品。让 ChatGPT 登录与
|
|
4
|
+
Responses API 路由使用同一个 CODEX_HOME,保留聊天历史。
|
|
5
|
+
|
|
6
|
+
当前版本 `0.1.0a7` 是 PyPI 候选发布包,尚未上传。Linux / Python 3.11+,
|
|
7
|
+
已验证 Codex 0.153.4 / 0.154.0;不是 OpenAI 官方产品。
|
|
8
|
+
|
|
9
|
+
新增命名 ChatGPT 账号和隔离测速:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
codex-mode login --account second # 保存登录,不立即启用
|
|
13
|
+
codex-mode accounts
|
|
14
|
+
codex-mode account-save --account first # 服务停止后,保存当前登录
|
|
15
|
+
codex-mode chatgpt --account second # 停止任务、暂停 goal、断开 Desktop 后切换
|
|
16
|
+
codex-mode speed # hello,gpt-5.6-sol / medium,各一次
|
|
17
|
+
codex-mode speed --rounds 3 --timeout 60
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
测速消耗额度/费用,使用空白临时目录,不发送旧聊天或代码,不切换当前线路。
|
|
21
|
+
结果为端到端完整回复耗时,包含 CLI 启动、排队和内部重试,不是首 token 延迟。
|
|
22
|
+
多轮交替顺序,并汇总成功次数和成功样本中位数;失败样本不算成功。
|
|
23
|
+
上游实际模型映射无法由本工具保证,hello 结果也不代表长任务吞吐。
|
|
24
|
+
ChatGPT 测试临时凭据可能触发令牌刷新;首次建议避开重要运行任务。
|
|
25
|
+
命名账号共享本地历史,不能同步云端任务,也不支持同目录双账号并发。
|
|
26
|
+
Python >= 3.11;仅支持 Linux。版本检查目前只接受 Codex 0.153.4 / 0.154.0;
|
|
27
|
+
实验接口可能变化,其他版本需要先完成兼容验证。
|
|
28
|
+
|
|
29
|
+
## 本地安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python3 -m venv .venv
|
|
33
|
+
.venv/bin/python -m pip install .
|
|
34
|
+
.venv/bin/codex-mode --version
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
不要覆盖已有稳定版的命令;先在独立虚拟环境验证。包名 `codex-mode` 暂定,
|
|
38
|
+
PyPI 名称可用性、许可证、作者信息和发布账户尚待确认。
|
|
39
|
+
|
|
40
|
+
## 推荐:逐步配置向导
|
|
41
|
+
|
|
42
|
+
先安装 Codex 并运行一次,确保 CODEX_HOME 已有 config.toml。手动停止所有任务、
|
|
43
|
+
暂停 goal,断开该机器的 Desktop;在服务器终端而不是运行中的 Codex 聊天执行:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
codex-mode setup
|
|
47
|
+
# 不带参数也启动向导;指定其他目录:
|
|
48
|
+
codex-mode setup --state-dir /absolute/path/to/codex-home
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
六步:检查环境 → 完整备份 → 扫描历史 provider → 隐藏输入 API key 和模型名
|
|
52
|
+
→ 复用或完成官方 ChatGPT 设备登录 → 隔离验证两种模式。逐步确认是否托管历史
|
|
53
|
+
provider、是否发送真实测试请求、是否最终启用路由。默认不激活路由。
|
|
54
|
+
旧任务只读取,不 resume、不启动 turn、不操作 goal。真实请求仅使用新建临时
|
|
55
|
+
任务和固定一句话,不发送旧聊天。跳过真实请求时不会宣称实际线路可用。
|
|
56
|
+
测试配置关闭 shell、unified exec 和 web search,移除生产 MCP/hooks 等设置。
|
|
57
|
+
参考官方 [鉴权](https://learn.chatgpt.com/docs/auth)、
|
|
58
|
+
[App Server](https://learn.chatgpt.com/docs/app-server) 和
|
|
59
|
+
[配置字段](https://learn.chatgpt.com/docs/config-file/config-reference)。
|
|
60
|
+
|
|
61
|
+
完整备份位于父目录/.codex-mode-backups/<目录名>-<时间>/home,含凭据、聊天、
|
|
62
|
+
数据库和插件,可能占用数十 GB,不要分享。目录 0700、普通文件 0600;SQLite
|
|
63
|
+
backup API 合并已提交 WAL。运行时 socket 不复制,符号链接不跟随,异常及原始
|
|
64
|
+
权限记录在 manifest.json。正式向导拒绝运行中的服务;测试 live capture 不是
|
|
65
|
+
跨文件一致性快照。验证只写元数据/结果,不在报告中写正文和 token。
|
|
66
|
+
|
|
67
|
+
逐个检查全部任务摘要,检查置顶任务和少量样本的最新轮次内容;不是逐字校验全部
|
|
68
|
+
历史,也不能保证 Desktop 每个筛选都显示所有任务。内置 openai 不覆盖,历史
|
|
69
|
+
provider/source/cwd 筛选仍可能隐藏任务,需按 ID 或相应筛选打开。缺失代码目录
|
|
70
|
+
会提示,工具不迁移代码。失败不启用新路由;工具配置/凭据会保留供排查,原主配置
|
|
71
|
+
和鉴权保持不变。
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
codex-mode scan
|
|
75
|
+
codex-mode restore --backup /absolute/path/to/backup-directory
|
|
76
|
+
codex-mode verify --backup /absolute/path/to/backup-directory
|
|
77
|
+
# 已有完整备份时重试验证,无需再次备份;加 --request-test 确认后测试真实请求
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
restore 需停止服务并确认,只还原配置和登录,先保存当前控制文件的撤销副本;不
|
|
81
|
+
覆盖聊天、数据库或代码。旧登录可能需重新授权。备份中不存在的控制文件会移除,
|
|
82
|
+
可从撤销副本找回。只分享 wheel 和 SHARE.md,不分享备份或真实 CODEX_HOME。
|
|
83
|
+
|
|
84
|
+
## 初始化与使用
|
|
85
|
+
|
|
86
|
+
先安装、初始化 Codex,确保选定 CODEX_HOME 中有 config.toml。
|
|
87
|
+
默认使用环境变量 CODEX_HOME,否则使用当前用户的 ~/.codex。
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# API 地址需要指向支持 Responses 的服务,不要在 URL 中放密钥。
|
|
91
|
+
codex-mode init --api-url https://api.example.com/v1 --prompt-key
|
|
92
|
+
# 或使用环境变量(该变量也必须对 Desktop 远程服务可见)
|
|
93
|
+
codex-mode init --api-url https://api.example.com/v1 --key-env MY_CODEX_API_KEY
|
|
94
|
+
|
|
95
|
+
codex-mode doctor
|
|
96
|
+
codex-mode login
|
|
97
|
+
codex-mode chatgpt --dry-run
|
|
98
|
+
codex-mode chatgpt
|
|
99
|
+
codex-mode api
|
|
100
|
+
codex-mode status
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`sub2api` 是 `api` 的兼容命令。`--state-dir DIR` 指定独立目录;
|
|
104
|
+
`--codex-bin PATH` 覆盖 Codex 可执行文件;`init --codex-bin PATH` 将其保存。
|
|
105
|
+
`init --import-api-key` 显式导入现有 auth.json 的 API key,不改现有鉴权。
|
|
106
|
+
可选 `init --health-url URL` 启用独立健康检查,默认不假定服务有 /health。
|
|
107
|
+
|
|
108
|
+
工具配置保存在 CODEX_HOME/codex-mode.json。隐藏输入的 API key 保存在
|
|
109
|
+
CODEX_HOME/auth-profiles/sub2api.auth.json(0600);不是加密保险库。
|
|
110
|
+
也可使用 key_env,不保存 key 值。`_key` 是凭据读取专用内部命令,不应手动调用。
|
|
111
|
+
安装路径及对应虚拟环境必须在 Codex 使用期间保持可访问,生成的凭据命令
|
|
112
|
+
使用该 Python 的绝对路径和 `-m codex_mode`。
|
|
113
|
+
|
|
114
|
+
## 中断与历史边界
|
|
115
|
+
|
|
116
|
+
普通切换前,请手动停止正在运行的任务、暂停 goal,并断开 Desktop。
|
|
117
|
+
工具只复查已加载会话是否空闲,不查询 goal,也不自动暂停或中断任务。
|
|
118
|
+
空闲服务使用 SIGTERM 请求退出,最多等待 30 秒;无法确认空闲时拒绝切换。
|
|
119
|
+
空闲 Desktop 代理仍在时,可用 --disconnect-idle;无法正常停止时用 --force。
|
|
120
|
+
显式 `codex-mode chatgpt --force`(或 `api --force`)绕过会话查询,要求终端确认,
|
|
121
|
+
先向当前用户/当前 CODEX_HOME 的既有服务与代理发送 SIGTERM,5 秒后仍未退出才
|
|
122
|
+
发送 SIGKILL。需要 Python/kernel 的 Linux pidfd 支持;不会终止新出现的连接。
|
|
123
|
+
force 模式不能保证未完成轮次完整保存,也不会暂停或清除 goal;外部后台进程可能
|
|
124
|
+
继续运行。切换完成后重连 Desktop,启动新配置服务。force 仅适合正常切换失败时使用。
|
|
125
|
+
未手动暂停的 goal 可能在重连后继续运行;工具不会改变 goal 状态。
|
|
126
|
+
已中断的轮次不会自动继续,已落盘代码修改不会撤销。
|
|
127
|
+
切换期间不要重新连接 Desktop 或提交新任务;外部客户端不受本工具锁约束,
|
|
128
|
+
状态复查只能缩小并发窗口,不能提供跨客户端事务保证。
|
|
129
|
+
|
|
130
|
+
重新连接 Desktop,在同一聊天检查进度并继续。训练、下载、nohup/systemd 等
|
|
131
|
+
外部进程不受会话中断统一管理,重新运行命令前检查它们。
|
|
132
|
+
|
|
133
|
+
默认只管理稳定 provider ID `direct`。历史 provider ID 不会自动统一,
|
|
134
|
+
CLI 列表可能仍按 provider 过滤;`resume --all` 只取消目录筛选。
|
|
135
|
+
使用 `init --provider-alias OpenAI --provider-alias sub2api` 显式允许工具在切换时
|
|
136
|
+
重写这些 provider 的配置,使它们跟随所选路由。它不会改历史 JSONL 或数据库。
|
|
137
|
+
这些别名原有的专有设置将被替换;其他 providers、项目设置、注释和多行值保留。
|
|
138
|
+
新配置使用文件凭据存储,替代 forced_login_method;不保留 keyring-only 设置。
|
|
139
|
+
|
|
140
|
+
默认日志精简,TTY 支持颜色,NO_COLOR / TERM=dumb 禁用颜色;
|
|
141
|
+
`--verbose` 查看任务 ID、备份路径等。`--dry-run` 仅验证配置转换,不验证网络、
|
|
142
|
+
任务状态或实际模型请求。
|
|
143
|
+
|
|
144
|
+
## 网络与发布
|
|
145
|
+
|
|
146
|
+
不自动配置 SSH、系统路由或中继。已有 SSH 转发可作为 api-url 指向的本地端口,
|
|
147
|
+
由用户另外管理。
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
python -m unittest discover -s tests -v
|
|
151
|
+
# 实际 Codex + 临时 CODEX_HOME + loopback 模拟 API,不使用真实凭据:
|
|
152
|
+
python tests/integration_smoke.py /absolute/path/to/codex
|
|
153
|
+
python -m build
|
|
154
|
+
python -m twine check dist/*
|
|
155
|
+
# 先确认名称/许可证/账号,再手动上传 TestPyPI;不要上传凭据或历史。
|
|
156
|
+
# python -m twine upload --repository testpypi dist/*
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
发布前待办见 RELEASE.md。安装不会读取或修改实际 Codex 登录;init 只更改
|
|
160
|
+
工具自己的配置/凭据,mode 命令才执行切换并备份 config.toml/auth.json。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Release candidate checklist
|
|
2
|
+
|
|
3
|
+
Prepared: Linux alpha package, configurable endpoint/state/binary, optional health
|
|
4
|
+
check, file/env credentials, format-preserving TOML changes, console entry point,
|
|
5
|
+
manual-stop switching, explicit force shutdown and regression tests.
|
|
6
|
+
|
|
7
|
+
Before a public release:
|
|
8
|
+
|
|
9
|
+
- Confirm distribution name ownership/availability; current name is provisional.
|
|
10
|
+
- MIT confirmed; attribution henrychi. LICENSE and SPDX metadata included.
|
|
11
|
+
- Configure a PyPI/TestPyPI account or trusted publisher; never commit tokens.
|
|
12
|
+
- Review the archive contents for private paths, credentials and conversation data.
|
|
13
|
+
- Validate TestPyPI install in a clean environment before uploading to PyPI.
|
|
14
|
+
- Add CI and broader verified Codex version coverage before calling this stable.
|
|
15
|
+
|
|
16
|
+
This project is independent, not endorsed by OpenAI. The package includes only
|
|
17
|
+
source/tests/docs/metadata, not the machine's CODEX_HOME or SSH relay credentials.
|
|
18
|
+
|
|
19
|
+
0.1.0a7 candidate validation: clean wheel installation; 97 tests (95 passed,
|
|
20
|
+
2 skipped for unavailable pidfd); twine checks passed for wheel and sdist;
|
|
21
|
+
archive privacy scan passed. Isolated ChatGPT hello completed in 8.44 seconds
|
|
22
|
+
with requested gpt-5.6-sol / medium. Live API benchmark and installed NR151
|
|
23
|
+
package migration are pending; do not describe both routes as live-verified yet.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# codex-mode 快速使用
|
|
2
|
+
|
|
3
|
+
Linux 内部试用版,Python 3.11+,支持 Codex 0.153.4 / 0.154.0。
|
|
4
|
+
Desktop SSH 用户在服务器安装;先运行一次 Codex,完成初始化。
|
|
5
|
+
|
|
6
|
+
## 安装与配置
|
|
7
|
+
|
|
8
|
+
解压分享包,在解压目录执行(需联网安装依赖):
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
python3 -m venv "$HOME/.local/share/codex-mode-venv"
|
|
12
|
+
"$HOME/.local/share/codex-mode-venv/bin/python" -m pip install ./codex_mode-0.1.0a7-py3-none-any.whl
|
|
13
|
+
export PATH="$HOME/.local/share/codex-mode-venv/bin:$PATH"
|
|
14
|
+
codex-mode setup
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
配置前停止任务、暂停 goal、断开 Desktop,并清理 OPENAI_API_KEY /
|
|
18
|
+
OPENAI_BASE_URL 等覆盖变量。准备 Responses API 地址、自己的 key 和支持的模型名。
|
|
19
|
+
向导会备份、扫描历史、引导登录并验证;真实请求测试可选,可能计费。
|
|
20
|
+
目录不是 ~/.codex 时,命令添加 `--state-dir /absolute/path/to/codex-home`。
|
|
21
|
+
|
|
22
|
+
## 日常切换
|
|
23
|
+
|
|
24
|
+
先停止任务、暂停 goal、断开 Desktop,然后执行:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
codex-mode api # API
|
|
28
|
+
codex-mode chatgpt # ChatGPT
|
|
29
|
+
codex-mode speed # 相同模型的 hello 完整响应耗时,真实请求可能计费
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
重连后在原任务继续。新终端需重新执行上面的 export PATH;不要移动或删除安装环境。
|
|
33
|
+
正常退出失败才用 `--force`:需要 Linux pidfd 支持,可能丢失未保存输出,
|
|
34
|
+
不暂停 goal,也不停止训练、下载等外部进程。
|
|
35
|
+
|
|
36
|
+
## 故障处理与安全
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
codex-mode verify --backup <备份目录> # 重试验证;加 --request-test 测试真实请求
|
|
40
|
+
codex-mode restore --backup <备份目录> # 停止服务后恢复配置,不覆盖聊天或代码
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- 切换影响整个 CODEX_HOME;聊天保留不保证所有客户端筛选都显示。
|
|
44
|
+
- 备份可能很大且含凭据,API key 文件为明文;不要分享备份、鉴权或聊天。
|
|
45
|
+
- 第三方 API 会接收实际任务上下文和代码,先确认公司政策;优先用 HTTPS。
|
|
46
|
+
- 工具不是 OpenAI 官方产品;网络中继另配,上游 overload 不由它修复。
|
|
47
|
+
- 多账号:login --account NAME 保存;accounts 查看;chatgpt --account NAME 切换。
|
|
48
|
+
- 测速不切换线路,包含 CLI 启动与重试;临时 ChatGPT 凭据可能刷新,首次避开重要任务。
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codex-mode"
|
|
7
|
+
version = "0.1.0a7"
|
|
8
|
+
description = "Independent Linux tool to switch Codex ChatGPT and API routes with shared history"
|
|
9
|
+
keywords = ["codex", "cli", "authentication", "benchmark"]
|
|
10
|
+
authors = [{name = "henrychi"}]
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.11"
|
|
15
|
+
dependencies = ["tomlkit>=0.13,<1"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
codex-mode = "codex_mode.cli:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
include-package-data = false
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Named ChatGPT credentials; history stays in the selected CODEX_HOME."""
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from . import engine as e
|
|
5
|
+
|
|
6
|
+
def path(root, name, create=True):
|
|
7
|
+
if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_-]{0,63}', name):
|
|
8
|
+
raise ValueError('账号名仅允许字母、数字、横线和下划线,最长 64 字符。')
|
|
9
|
+
folder=root/'auth-profiles'/'accounts'
|
|
10
|
+
if (root/'auth-profiles').is_symlink() or folder.is_symlink():
|
|
11
|
+
raise RuntimeError('账号目录不能是符号链接。')
|
|
12
|
+
if create:folder.mkdir(parents=True,mode=0o700,exist_ok=True)
|
|
13
|
+
target=folder/(name+'.json')
|
|
14
|
+
if target.is_symlink():raise RuntimeError('账号文件不能是符号链接。')
|
|
15
|
+
return target
|
|
16
|
+
|
|
17
|
+
def identity(auth):
|
|
18
|
+
return (auth.get('tokens') or {}).get('account_id')
|
|
19
|
+
|
|
20
|
+
def save(root, name):
|
|
21
|
+
auth=e.read_auth(root/'auth.json')
|
|
22
|
+
if not e.chat_auth(auth):raise RuntimeError('当前目录没有 ChatGPT 登录。')
|
|
23
|
+
target=path(root,name)
|
|
24
|
+
if target.exists():raise RuntimeError('账号名已存在,请使用新名称,避免覆盖凭据。')
|
|
25
|
+
e.atomic(target,json.dumps(auth))
|
|
26
|
+
settings=e.read_auth(root/'codex-mode.json')
|
|
27
|
+
settings['active_account']=name
|
|
28
|
+
e.atomic(root/'codex-mode.json',json.dumps(settings))
|
|
29
|
+
e.say('✓ 当前登录已保存为 '+name+'(未切换线路)。','ok')
|
|
30
|
+
|
|
31
|
+
def login(root,name):
|
|
32
|
+
target=path(root,name)
|
|
33
|
+
if target.exists():raise RuntimeError('账号名已存在,请使用新名称。')
|
|
34
|
+
# Official login runs in an empty staging home, never the active home.
|
|
35
|
+
import tempfile
|
|
36
|
+
import shutil
|
|
37
|
+
staging=e.Path(tempfile.mkdtemp(prefix='account-login-',dir=target.parent))
|
|
38
|
+
try:
|
|
39
|
+
e.atomic(staging/'config.toml','model_provider="openai"\ncli_auth_credentials_store="file"\n')
|
|
40
|
+
result=e.subprocess.run([e.BIN,'login','--device-auth'],env=e.codex_env(staging))
|
|
41
|
+
if result.returncode:raise RuntimeError('登录未完成,当前登录未修改。')
|
|
42
|
+
auth=e.read_auth(staging/'auth.json')
|
|
43
|
+
if not e.chat_auth(auth):raise RuntimeError('未获得 ChatGPT 登录凭据。')
|
|
44
|
+
e.atomic(target,json.dumps(auth))
|
|
45
|
+
e.say('✓ 已保存账号 '+name+';尚未启用。','ok')
|
|
46
|
+
finally:shutil.rmtree(staging)
|
|
47
|
+
|
|
48
|
+
def listing(root):
|
|
49
|
+
settings=e.read_auth(root/'codex-mode.json')
|
|
50
|
+
folder=root/'auth-profiles'/'accounts'
|
|
51
|
+
if folder.is_symlink():raise RuntimeError('账号目录不能是符号链接。')
|
|
52
|
+
names=sorted(p.stem for p in folder.glob('*.json') if not p.is_symlink())
|
|
53
|
+
for name in names:
|
|
54
|
+
print(name+(' · 当前选中' if name==settings.get('active_account') else ''))
|
|
55
|
+
if not names:print('尚无命名账号。运行 login --account NAME。')
|
|
56
|
+
|
|
57
|
+
def switch(root,name,force=False,disconnect_idle=False):
|
|
58
|
+
target=path(root,name)
|
|
59
|
+
selected=e.read_auth(target)
|
|
60
|
+
if not e.chat_auth(selected):raise RuntimeError('账号未登录:'+name)
|
|
61
|
+
# Stop only servers associated with this home, before changing any credentials.
|
|
62
|
+
if force:e.force_stop(root)
|
|
63
|
+
else:e.ensure_idle(root,disconnect_idle=disconnect_idle,allow_interrupt=False)
|
|
64
|
+
originals={p:p.read_bytes() if p.exists() else None for p in
|
|
65
|
+
(root/'auth.json',root/'codex-mode.json',root/'config.toml',root/'auth-profiles'/'chatgpt.auth.json')}
|
|
66
|
+
settings=e.read_auth(root/'codex-mode.json')
|
|
67
|
+
current=e.read_auth(root/'auth.json')
|
|
68
|
+
old=settings.get('active_account')
|
|
69
|
+
if not old and e.chat_auth(current):
|
|
70
|
+
# Preserve the pre-existing login when adopting named accounts.
|
|
71
|
+
old='previous-'+e.datetime.datetime.now().strftime('%Y%m%d-%H%M%S-%f')
|
|
72
|
+
e.atomic(path(root,old),json.dumps(current))
|
|
73
|
+
if old and e.chat_auth(current):
|
|
74
|
+
previous=path(root,old)
|
|
75
|
+
stored=e.read_auth(previous)
|
|
76
|
+
if not identity(current) or identity(current)!=identity(stored):
|
|
77
|
+
raise RuntimeError('当前登录与记录账号不一致,已取消,避免覆盖其他账号。')
|
|
78
|
+
e.atomic(previous,json.dumps(current))
|
|
79
|
+
# Reload: switching to the same account must use refreshed credentials.
|
|
80
|
+
selected=e.read_auth(target)
|
|
81
|
+
try:
|
|
82
|
+
e.atomic(root/'auth.json',json.dumps(selected))
|
|
83
|
+
e.switch(root,'chatgpt',disconnect_idle=disconnect_idle,force=False)
|
|
84
|
+
settings['active_account']=name
|
|
85
|
+
e.atomic(root/'codex-mode.json',json.dumps(settings))
|
|
86
|
+
except BaseException:
|
|
87
|
+
for p,data in originals.items():
|
|
88
|
+
if data is None:p.unlink(missing_ok=True)
|
|
89
|
+
else:e.atomic(p,data)
|
|
90
|
+
raise
|
|
91
|
+
e.say('账号:'+name)
|