codex-safe-switch 1.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_safe_switch-1.0.0/.github/workflows/ci.yml +59 -0
- codex_safe_switch-1.0.0/.github/workflows/release.yml +79 -0
- codex_safe_switch-1.0.0/.gitignore +19 -0
- codex_safe_switch-1.0.0/LICENSE +21 -0
- codex_safe_switch-1.0.0/PKG-INFO +201 -0
- codex_safe_switch-1.0.0/README.en.md +172 -0
- codex_safe_switch-1.0.0/README.md +180 -0
- codex_safe_switch-1.0.0/alfred/codex-safe-switch.alfredworkflow +0 -0
- codex_safe_switch-1.0.0/alfred/icon.png +0 -0
- codex_safe_switch-1.0.0/alfred/info.plist +173 -0
- codex_safe_switch-1.0.0/examples/relay-profile/auth.json +1 -0
- codex_safe_switch-1.0.0/examples/relay-profile/provider.toml +12 -0
- codex_safe_switch-1.0.0/pyproject.toml +33 -0
- codex_safe_switch-1.0.0/src/codex_safe_switch/__init__.py +1 -0
- codex_safe_switch-1.0.0/src/codex_safe_switch/_swap.py +69 -0
- codex_safe_switch-1.0.0/src/codex_safe_switch/cli.py +1770 -0
- codex_safe_switch-1.0.0/src/codex_safe_switch/picker.py +145 -0
- codex_safe_switch-1.0.0/tests/test_cli.py +867 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
name: Test Python ${{ matrix.python-version }}
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ['3.10', '3.11', '3.12', '3.13']
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Check out repository
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v6
|
|
26
|
+
with:
|
|
27
|
+
enable-cache: true
|
|
28
|
+
|
|
29
|
+
- name: Set up Python
|
|
30
|
+
uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
|
|
34
|
+
- name: Run unit tests
|
|
35
|
+
run: uv run python -m unittest tests.test_cli
|
|
36
|
+
|
|
37
|
+
build:
|
|
38
|
+
name: Build package
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
|
|
41
|
+
steps:
|
|
42
|
+
- name: Check out repository
|
|
43
|
+
uses: actions/checkout@v4
|
|
44
|
+
|
|
45
|
+
- name: Install uv
|
|
46
|
+
uses: astral-sh/setup-uv@v6
|
|
47
|
+
with:
|
|
48
|
+
enable-cache: true
|
|
49
|
+
|
|
50
|
+
- name: Set up Python
|
|
51
|
+
uses: actions/setup-python@v5
|
|
52
|
+
with:
|
|
53
|
+
python-version: '3.12'
|
|
54
|
+
|
|
55
|
+
- name: Build distributions
|
|
56
|
+
run: uv build
|
|
57
|
+
|
|
58
|
+
- name: Check distributions
|
|
59
|
+
run: uvx twine check dist/*
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
name: Build distributions
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Check out repository
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v6
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
|
|
28
|
+
- name: Set up Python
|
|
29
|
+
uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: '3.12'
|
|
32
|
+
|
|
33
|
+
- name: Verify tag matches package version
|
|
34
|
+
run: |
|
|
35
|
+
PACKAGE_VERSION=$(uv run python - <<'PY'
|
|
36
|
+
import tomllib
|
|
37
|
+
with open('pyproject.toml', 'rb') as f:
|
|
38
|
+
print(tomllib.load(f)['project']['version'])
|
|
39
|
+
PY
|
|
40
|
+
)
|
|
41
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
42
|
+
if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then
|
|
43
|
+
echo "Tag v$TAG_VERSION does not match pyproject version $PACKAGE_VERSION" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
- name: Run unit tests
|
|
48
|
+
run: uv run python -m unittest tests.test_cli
|
|
49
|
+
|
|
50
|
+
- name: Build distributions
|
|
51
|
+
run: uv build
|
|
52
|
+
|
|
53
|
+
- name: Check distributions
|
|
54
|
+
run: uvx twine check dist/*
|
|
55
|
+
|
|
56
|
+
- name: Upload distributions
|
|
57
|
+
uses: actions/upload-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: python-package-distributions
|
|
60
|
+
path: dist/
|
|
61
|
+
|
|
62
|
+
publish:
|
|
63
|
+
name: Publish distributions to PyPI
|
|
64
|
+
needs: build
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
|
|
67
|
+
steps:
|
|
68
|
+
- name: Download distributions
|
|
69
|
+
uses: actions/download-artifact@v4
|
|
70
|
+
with:
|
|
71
|
+
name: python-package-distributions
|
|
72
|
+
path: dist/
|
|
73
|
+
|
|
74
|
+
- name: Publish to PyPI
|
|
75
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
76
|
+
with:
|
|
77
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
78
|
+
attestations: false
|
|
79
|
+
skip-existing: true
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# macOS
|
|
2
|
+
.DS_Store
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*$py.class
|
|
8
|
+
*.egg-info/
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
.venv/
|
|
12
|
+
|
|
13
|
+
# uv
|
|
14
|
+
.uv/
|
|
15
|
+
uv.lock
|
|
16
|
+
|
|
17
|
+
# Never commit real credentials at top level (allow placeholder examples)
|
|
18
|
+
auth.json
|
|
19
|
+
!examples/**/auth.json
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kadaliao
|
|
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,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codex-safe-switch
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Switch between OpenAI Codex CLI provider configurations (official ChatGPT login, third-party relays, multiple API keys). CLI + Alfred workflow.
|
|
5
|
+
Project-URL: Homepage, https://github.com/kadaliao/codex-safe-switch
|
|
6
|
+
Project-URL: Issues, https://github.com/kadaliao/codex-safe-switch/issues
|
|
7
|
+
Author: kadaliao
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: alfred,cli,codex,openai,profile
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: tomlkit>=0.13
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# codex-safe-switch
|
|
23
|
+
|
|
24
|
+
[](https://pypi.org/project/codex-safe-switch/)
|
|
25
|
+
[](https://github.com/kadaliao/codex-safe-switch/actions/workflows/ci.yml)
|
|
26
|
+
|
|
27
|
+
中文 | [English](README.en.md)
|
|
28
|
+
|
|
29
|
+
一键切换 [OpenAI Codex CLI](https://github.com/openai/codex) 的 provider 配置:官方 ChatGPT 登录、第三方 relay、多组 API key,都可以放进 profile 里。提供命令行工具,并可选配 Alfred workflow。
|
|
30
|
+
|
|
31
|
+
每个 profile 只接管 `~/.codex/config.toml` 里的 provider 片段(model、`[model_providers.*]`、认证方式)。只有确实需要自己认证文件的 profile 才保存 `auth.json`;使用 `env_key` 的 relay 可以继续依赖环境变量,不覆盖官方 ChatGPT 登录缓存。你的本地状态(trusted projects、plugins、marketplaces、MCP servers、TUI 偏好等)会在切换时保留。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
需要先安装 [`uv`](https://github.com/astral-sh/uv)。
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv tool install codex-safe-switch
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
安装后会把 `codex-safe-switch` 放到 `$PATH`(默认是 `~/.local/bin/`)。如果 shell 找不到命令,运行一次:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv tool update-shell
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
后续升级和卸载:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv tool upgrade codex-safe-switch
|
|
51
|
+
uv tool uninstall codex-safe-switch
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 免安装试用
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uvx --from codex-safe-switch codex-safe-switch ls
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`uvx` 会为单次调用解析并缓存临时环境,适合快速试用;如果你要频繁使用,尤其是配合 Alfred,建议用 `uv tool install`。
|
|
61
|
+
|
|
62
|
+
### 安装开发版
|
|
63
|
+
|
|
64
|
+
想在 PyPI 发版前使用 GitHub 最新提交,可以直接安装仓库版本:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv tool install git+https://github.com/kadaliao/codex-safe-switch.git
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Alfred(可选)
|
|
71
|
+
|
|
72
|
+
执行 `uv tool install` 后,双击 `alfred/codex-safe-switch.alfredworkflow` 导入 Alfred。触发关键词是 `cx`。
|
|
73
|
+
|
|
74
|
+
workflow 默认调用 `$HOME/.local/bin/codex-safe-switch`。如果你的 `uv tool install` 把命令装到了别处,可以用 `uv tool dir --bin` 查看路径,然后修改 workflow plist 里的两个 script block。
|
|
75
|
+
|
|
76
|
+
## 命令
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
codex-safe-switch # 交互式选择器(↑/↓,回车切换,q 取消)
|
|
80
|
+
codex-safe-switch ls # 列出 profiles,★ 表示当前 active
|
|
81
|
+
codex-safe-switch current # 打印当前 active profile
|
|
82
|
+
codex-safe-switch official # 切回官方 OpenAI ChatGPT 登录
|
|
83
|
+
codex-safe-switch openai # `official` 的别名
|
|
84
|
+
codex-safe-switch use [name] # 加载 <name>;不传 name 时进入选择器
|
|
85
|
+
codex-safe-switch save <name> # 把当前 ~/.codex 状态保存成 <name>
|
|
86
|
+
codex-safe-switch show <name> # 打印 <name> 的 provider.toml 和 auth.json key 名
|
|
87
|
+
codex-safe-switch state <name> # 查看/设置 profile 的 session-state 作用域
|
|
88
|
+
codex-safe-switch restart-codex
|
|
89
|
+
# 终止 Codex app/server 进程,让配置立即生效
|
|
90
|
+
codex-safe-switch merge-history --dry-run
|
|
91
|
+
# 预览历史 metadata 修复,不写入文件
|
|
92
|
+
codex-safe-switch doctor-history
|
|
93
|
+
# 只读检查当前历史 provider/model 状态
|
|
94
|
+
codex-safe-switch rm <name> # 删除 profile(不允许删除 active profile)
|
|
95
|
+
codex-safe-switch alfred-list # 输出 Alfred Script Filter JSON
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
当 stdin/stdout 不是 TTY(管道、脚本等)时,选择器会自动降级成数字菜单。
|
|
99
|
+
|
|
100
|
+
## 首次使用
|
|
101
|
+
|
|
102
|
+
如果 `~/.codex/profiles/` 里还没有 profile,第一次运行 `codex-safe-switch`、`codex-safe-switch ls` 或 Alfred workflow 时,工具会自动导入当前 `~/.codex/config.toml` 的 provider 状态,并按 profile 是否需要自己认证来决定是否保存 `auth.json`。
|
|
103
|
+
|
|
104
|
+
- 官方 ChatGPT 登录会导入成隐藏的 `official` profile。
|
|
105
|
+
- relay/API-key 配置会导入成普通 profile,名字来自 `model_provider`,例如 `relay`。
|
|
106
|
+
- 如果 Codex 还没有配置过,CLI 会提示你先正常配置一次 Codex,或者在手动配置 provider 后运行 `codex-safe-switch save <name>`。
|
|
107
|
+
|
|
108
|
+
如果需要让 Codex 桌面 app 或 app server 立刻读到新配置,可以使用:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
codex-safe-switch use <name> --restart-codex
|
|
112
|
+
codex-safe-switch official --restart-codex
|
|
113
|
+
codex-safe-switch restart-codex
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`restart-codex` 会终止匹配到的 Codex app/server 进程,并避开 `codex-safe-switch` 自己。
|
|
117
|
+
|
|
118
|
+
## 官方 OpenAI 快捷入口
|
|
119
|
+
|
|
120
|
+
`codex-safe-switch official` 可以一键切回官方 OpenAI ChatGPT 登录。
|
|
121
|
+
|
|
122
|
+
- 工具会维护隐藏快照:`~/.codex/profiles/.official/`。
|
|
123
|
+
- 第一次从官方登录切换到其他 profile 时,会自动刷新这个官方快照。
|
|
124
|
+
- 如果你更喜欢按 provider 名称输入,也可以用 `codex-safe-switch openai`。
|
|
125
|
+
|
|
126
|
+
## 默认共享历史
|
|
127
|
+
|
|
128
|
+
每次执行 `use` / `official` 后,`codex-safe-switch` 都会自动把本地 Codex 历史 metadata 对齐到当前 provider 和 model identity。
|
|
129
|
+
|
|
130
|
+
- 正常切换 profile 时不需要再手动记 `merge-history`。
|
|
131
|
+
- 在 relay profile 和官方 OpenAI 登录之间切换时,历史会继续可见,包括会按 model id 过滤的界面。
|
|
132
|
+
- 如果 `session_index.jsonl` 落后于 SQLite 里的最新 thread,切换时会从 `state_5.sqlite` 补追加索引项,避免移动端历史列表停在旧时间点。
|
|
133
|
+
- 如果本机已经开启过 Codex remote-control,切换时还会顺手检查移动端/桌面远程连接依赖的 managed app-server。遇到旧的 unmanaged unix app-server 占住 socket 时会自动结束旧进程并重试;如果 Desktop 还留着旧 SSH remote proxy,会只停止这些 proxy 进程,让下次连接回到当前 MBP;如果缺少官方 standalone 安装,会打印 `curl -fsSL https://chatgpt.com/codex/install.sh | sh` 这种可执行修复命令。
|
|
134
|
+
- 如果你只想修复 provider,不想覆盖历史里的 model 值,可以继续用 `merge-history --keep-models`。
|
|
135
|
+
- `merge-history --dry-run` 会报告将要更新的 rollout 文件数、行数、SQLite rows,以及会创建的备份路径,但不会写入文件。
|
|
136
|
+
- `doctor-history` 是只读诊断命令,会汇总 active profile、当前 provider/model、session-state 模式、SQLite `threads` 分布、最近线程、计划对齐数量和漂移状态。
|
|
137
|
+
|
|
138
|
+
## Profile 格式
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
~/.codex/profiles/
|
|
142
|
+
├── .active # 明文:当前 active profile 名
|
|
143
|
+
├── .official/
|
|
144
|
+
│ ├── auth.json # 切换时完整复制到 ~/.codex/auth.json
|
|
145
|
+
│ └── provider.toml # 官方登录的 provider 片段
|
|
146
|
+
└── myrelay/
|
|
147
|
+
├── auth.json # 可选;只有该 profile 自带 API key 或 ChatGPT token 时需要
|
|
148
|
+
└── provider.toml # 只包含 provider 相关字段(见 examples/)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
profile 会接管下列顶层 key 和 table;`~/.codex/config.toml` 中其他内容会保留:
|
|
152
|
+
|
|
153
|
+
- `model`, `model_provider`, `model_reasoning_effort`, `model_reasoning_summary`, `model_verbosity`
|
|
154
|
+
- `wire_api`, `disable_response_storage`, `preferred_auth_method`
|
|
155
|
+
- `[model_providers.*]`
|
|
156
|
+
|
|
157
|
+
## 添加 relay profile
|
|
158
|
+
|
|
159
|
+
1. 先按正常方式在 `~/.codex/config.toml` 里配置 relay,并确认 `codex` 可以工作。
|
|
160
|
+
2. 如果 relay 用环境变量提供 key,推荐在 provider 里写 `requires_openai_auth = false` 和 `env_key = "..."`。这种 profile 不需要 `auth.json`,切换时会保留当前的官方 ChatGPT 登录缓存,方便 Codex 远程连接继续使用同一个 ChatGPT 账号。
|
|
161
|
+
3. 运行 `codex-safe-switch save <name>`,把 provider 片段保存成新 profile。只有 provider 明确需要 OpenAI/ChatGPT auth,或没有声明 `requires_openai_auth = false` 的旧式 API-key 配置,才会保存 `auth.json`。
|
|
162
|
+
4. 后续用 Alfred 的 `cx` 或 `codex-safe-switch use <name>` 随时切换。
|
|
163
|
+
|
|
164
|
+
`requires_openai_auth = false` 只说明这个 relay profile 不需要接管 `auth.json`。移动端能不能持续同步历史,取决于本机的官方 Codex remote-control/app-server 链路是否健康;这条链路需要官方 standalone install,而不是把 token 复制进每个 profile。
|
|
165
|
+
|
|
166
|
+
也可以手写 profile 文件,参考 `examples/relay-profile/`。
|
|
167
|
+
|
|
168
|
+
## 环境变量
|
|
169
|
+
|
|
170
|
+
| 变量 | 默认值 | 用途 |
|
|
171
|
+
| -------------------- | ------------------ | -------------------------- |
|
|
172
|
+
| `CODEX_PROFILE_ROOT` | `~/.codex/profiles` | profiles 存放位置 |
|
|
173
|
+
| `CODEX_HOME` | `~/.codex` | 要写入的 Codex 配置目录 |
|
|
174
|
+
|
|
175
|
+
## 发布
|
|
176
|
+
|
|
177
|
+
PyPI 包发布在 [`codex-safe-switch`](https://pypi.org/project/codex-safe-switch/)。
|
|
178
|
+
|
|
179
|
+
当前 GitHub Actions 触发规则:
|
|
180
|
+
|
|
181
|
+
- push 到 `main` 或提交 PR:运行 CI,包含 Python 单测、`uv build`、`twine check`。
|
|
182
|
+
- 推送 `v*` tag:运行 `Publish to PyPI` workflow,校验 tag 版本和 `pyproject.toml` 版本一致后,构建并发布到 PyPI。
|
|
183
|
+
- Alfred workflow:目前没有 GitHub Actions 自动构建;仓库里提交的是现成的 `alfred/codex-safe-switch.alfredworkflow`。
|
|
184
|
+
|
|
185
|
+
发版步骤:
|
|
186
|
+
|
|
187
|
+
1. 更新 `pyproject.toml` 里的 `version`。
|
|
188
|
+
2. 运行 `uv run python -m unittest tests.test_cli` 和 `uv build`。
|
|
189
|
+
3. 提交版本变更并推送 `main`。
|
|
190
|
+
4. 创建并推送匹配的 tag:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
git tag vX.Y.Z
|
|
194
|
+
git push origin vX.Y.Z
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`Publish to PyPI` workflow 会验证 tag 版本、运行测试、构建 wheel/sdist、检查分发包并发布到 PyPI。需要手动发布时,也可以使用 `uvx twine upload dist/*`。
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# codex-safe-switch
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/codex-safe-switch/)
|
|
4
|
+
[](https://github.com/kadaliao/codex-safe-switch/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
[中文](README.md) | English
|
|
7
|
+
|
|
8
|
+
One-key switch between [OpenAI Codex CLI](https://github.com/openai/codex) configurations — official ChatGPT login, third-party relays, multiple API keys, whatever. CLI + optional Alfred workflow.
|
|
9
|
+
|
|
10
|
+
Each profile owns the *provider* slice of `~/.codex/config.toml` (model, `[model_providers.*]`, auth method). A profile stores `auth.json` only when it really owns an auth file; relays that use `env_key` can rely on environment variables and leave the official ChatGPT login cache in place. Your local state (trusted projects, plugins, marketplaces, MCP servers, TUI prefs) is left untouched on every switch.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Requires [`uv`](https://github.com/astral-sh/uv).
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv tool install codex-safe-switch
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This puts `codex-safe-switch` on `$PATH` (default `~/.local/bin/`). Run `uv tool update-shell` once if your shell can't find it.
|
|
21
|
+
|
|
22
|
+
Upgrade later with `uv tool upgrade codex-safe-switch`; uninstall with `uv tool uninstall codex-safe-switch`.
|
|
23
|
+
|
|
24
|
+
### No-install (one-off)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uvx --from codex-safe-switch codex-safe-switch ls
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`uvx` resolves and caches an ephemeral environment per invocation. Convenient for trying it out, slower for hot paths like Alfred — use `uv tool install` if you want the workflow to feel snappy.
|
|
31
|
+
|
|
32
|
+
### Development version
|
|
33
|
+
|
|
34
|
+
Install directly from GitHub when you want the latest commit before it is released to PyPI:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv tool install git+https://github.com/kadaliao/codex-safe-switch.git
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Alfred (optional)
|
|
41
|
+
|
|
42
|
+
After `uv tool install`, double-click `alfred/codex-safe-switch.alfredworkflow`. Trigger with keyword `cx`.
|
|
43
|
+
|
|
44
|
+
The workflow calls `$HOME/.local/bin/codex-safe-switch`; if `uv tool install` put the binary elsewhere (`uv tool dir --bin` to check), edit the two `script` blocks in the workflow's plist accordingly.
|
|
45
|
+
|
|
46
|
+
## CLI
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
codex-safe-switch # interactive picker (↑/↓, enter to switch, q to cancel)
|
|
50
|
+
codex-safe-switch ls # list profiles, ★ marks the active one
|
|
51
|
+
codex-safe-switch current # print the active profile
|
|
52
|
+
codex-safe-switch official # switch back to official OpenAI ChatGPT login
|
|
53
|
+
codex-safe-switch openai # alias of `official`
|
|
54
|
+
codex-safe-switch use [name] # load <name>; omit for the picker
|
|
55
|
+
codex-safe-switch save <name> # snapshot the current ~/.codex state as <name>
|
|
56
|
+
codex-safe-switch show <name> # print <name>'s provider.toml + auth.json key names
|
|
57
|
+
codex-safe-switch state <name> # show/set the session-state scope for a profile
|
|
58
|
+
codex-safe-switch restart-codex
|
|
59
|
+
# terminate Codex app/server processes so config changes take effect
|
|
60
|
+
codex-safe-switch merge-history --dry-run
|
|
61
|
+
# preview history metadata changes without writing files
|
|
62
|
+
codex-safe-switch doctor-history
|
|
63
|
+
# inspect current history provider/model state read-only
|
|
64
|
+
codex-safe-switch rm <name> # delete profile (the active one is protected)
|
|
65
|
+
codex-safe-switch alfred-list # JSON for Alfred Script Filter
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The picker auto-falls back to a numeric menu when stdin/stdout aren't TTYs (pipes, scripts).
|
|
69
|
+
|
|
70
|
+
## First run
|
|
71
|
+
|
|
72
|
+
If `~/.codex/profiles/` has no profiles yet, `codex-safe-switch` automatically imports the current
|
|
73
|
+
`~/.codex/config.toml` provider state the first time you run `codex-safe-switch`, `codex-safe-switch ls`,
|
|
74
|
+
or the Alfred workflow, and stores `auth.json` only when that profile owns its own auth.
|
|
75
|
+
|
|
76
|
+
- Official ChatGPT login is imported as the hidden `official` profile.
|
|
77
|
+
- Relay/API-key configs are imported as a regular profile named from `model_provider` (for example `relay`).
|
|
78
|
+
- If Codex has not been configured yet, the CLI explains that you need to configure Codex once or run
|
|
79
|
+
`codex-safe-switch save <name>` after setting up the provider manually.
|
|
80
|
+
|
|
81
|
+
When you need the Codex desktop app or app server to pick up a switch immediately, use:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
codex-safe-switch use <name> --restart-codex
|
|
85
|
+
codex-safe-switch official --restart-codex
|
|
86
|
+
codex-safe-switch restart-codex
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The restart command terminates matching Codex app/server processes while avoiding `codex-safe-switch` itself.
|
|
90
|
+
|
|
91
|
+
## Official OpenAI shortcut
|
|
92
|
+
|
|
93
|
+
`codex-safe-switch official` is the one-step way back to the official OpenAI ChatGPT login.
|
|
94
|
+
|
|
95
|
+
- The tool keeps a hidden `~/.codex/profiles/.official/` snapshot for the official config/auth.
|
|
96
|
+
- The first time you switch away from an official OpenAI session, that snapshot is refreshed automatically.
|
|
97
|
+
- `codex-safe-switch openai` is an alias if you prefer typing the provider name directly.
|
|
98
|
+
|
|
99
|
+
## Shared history by default
|
|
100
|
+
|
|
101
|
+
After every `use` / `official` switch, `codex-safe-switch` automatically aligns local Codex history metadata to the active provider and model identity.
|
|
102
|
+
|
|
103
|
+
- You no longer need to remember `merge-history` during normal profile switching.
|
|
104
|
+
- This keeps session history visible when moving between relay profiles and the official OpenAI login, including surfaces that filter by model id.
|
|
105
|
+
- If `session_index.jsonl` has fallen behind the latest threads in SQLite, the switch appends repaired index entries from `state_5.sqlite` so mobile history lists do not stay pinned to an older point in time.
|
|
106
|
+
- If this host has used Codex remote-control before, the switch also checks the managed app-server path that desktop/mobile remote access depends on. It retries through the managed daemon when an old unmanaged unix app-server owns the socket; if Desktop still has old SSH remote proxy processes, it stops only those proxy processes so the next connection returns to the current Mac; and it prints the official standalone install command when that managed install is missing.
|
|
107
|
+
- `merge-history --keep-models` still exists if you want a provider-only repair and need to preserve historical per-thread model ids.
|
|
108
|
+
- `merge-history --dry-run` reports rollout files/lines, SQLite rows, and the backup path it would create without writing anything.
|
|
109
|
+
- `doctor-history` is read-only and summarizes the active profile, current provider/model, session-state mode, SQLite `threads` distribution, recent threads, planned alignment counts, and provider/model drift.
|
|
110
|
+
|
|
111
|
+
## Profile format
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
~/.codex/profiles/
|
|
115
|
+
├── .active # plaintext: name of the active profile
|
|
116
|
+
├── chatgpt-official/
|
|
117
|
+
│ ├── auth.json # full file copied into ~/.codex/auth.json
|
|
118
|
+
│ └── provider.toml # empty = use ChatGPT login
|
|
119
|
+
└── myrelay/
|
|
120
|
+
├── auth.json # optional; only needed when the profile owns a key/token
|
|
121
|
+
└── provider.toml # only provider-related keys (see examples/)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The following top-level keys + tables are owned by a profile (swapped on `use`); everything else in `~/.codex/config.toml` is preserved:
|
|
125
|
+
|
|
126
|
+
- `model`, `model_provider`, `model_reasoning_effort`, `model_reasoning_summary`, `model_verbosity`
|
|
127
|
+
- `wire_api`, `disable_response_storage`, `preferred_auth_method`
|
|
128
|
+
- `[model_providers.*]`
|
|
129
|
+
|
|
130
|
+
## Adding a relay profile
|
|
131
|
+
|
|
132
|
+
1. Configure the relay normally in `~/.codex/config.toml` and verify `codex` works.
|
|
133
|
+
2. For a relay whose key comes from the environment, prefer `requires_openai_auth = false` plus `env_key = "..."`. That profile does not need `auth.json`; switching to it preserves the current official ChatGPT login cache so Codex remote connections can keep using the same ChatGPT account.
|
|
134
|
+
3. `codex-safe-switch save <name>` — snapshots the provider slice into a new profile. It only stores `auth.json` when the provider explicitly needs OpenAI/ChatGPT auth, or for legacy API-key configs that do not declare `requires_openai_auth = false`.
|
|
135
|
+
4. `cx` in Alfred (or `codex-safe-switch use <name>`) to switch anytime.
|
|
136
|
+
|
|
137
|
+
`requires_openai_auth = false` only means the relay profile does not own `auth.json`. Mobile history sync still depends on a healthy official Codex remote-control/app-server install; copying tokens into each profile is not the durable fix.
|
|
138
|
+
|
|
139
|
+
Or build the files by hand — see `examples/relay-profile/`.
|
|
140
|
+
|
|
141
|
+
## Env overrides
|
|
142
|
+
|
|
143
|
+
| Var | Default | Purpose |
|
|
144
|
+
| -------------------- | -------------------- | --------------------------------- |
|
|
145
|
+
| `CODEX_PROFILE_ROOT` | `~/.codex/profiles` | where profiles live |
|
|
146
|
+
| `CODEX_HOME` | `~/.codex` | the codex config dir to write |
|
|
147
|
+
|
|
148
|
+
## Releasing
|
|
149
|
+
|
|
150
|
+
Packages are published on PyPI as [`codex-safe-switch`](https://pypi.org/project/codex-safe-switch/).
|
|
151
|
+
|
|
152
|
+
Current GitHub Actions triggers:
|
|
153
|
+
|
|
154
|
+
- Pushes to `main` and pull requests run CI: Python unit tests, `uv build`, and `twine check`.
|
|
155
|
+
- Pushing a `v*` tag runs the `Publish to PyPI` workflow. It verifies the tag matches `pyproject.toml`, builds the package, and publishes to PyPI.
|
|
156
|
+
- The Alfred workflow is not built by GitHub Actions today; the repository commits the ready-to-import `alfred/codex-safe-switch.alfredworkflow` file.
|
|
157
|
+
|
|
158
|
+
1. Update `version` in `pyproject.toml`.
|
|
159
|
+
2. Run `uv run python -m unittest tests.test_cli` and `uv build`.
|
|
160
|
+
3. Commit the version bump and push `main`.
|
|
161
|
+
4. Create and push a matching tag:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
git tag vX.Y.Z
|
|
165
|
+
git push origin vX.Y.Z
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The `Publish to PyPI` workflow verifies that the tag version matches `pyproject.toml`, runs tests, builds the wheel and sdist, checks the distributions, and publishes them to PyPI. Manual local publishing is still possible with `uvx twine upload dist/*` when needed.
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|