codex-switch-helper 1.0.0 → 1.0.1
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.
- package/README.md +11 -0
- package/codex-switch +139 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,15 @@ csw list # 验证
|
|
|
36
36
|
|
|
37
37
|
`install.sh` 只创建软链,不碰 `~/.codex/` 里任何现有内容。
|
|
38
38
|
|
|
39
|
+
### 首次使用
|
|
40
|
+
|
|
41
|
+
若 `~/.codex/profiles/` 还不存在,运行 `csw` / `csw list` 时会**自动**根据当前
|
|
42
|
+
`config.toml` + `auth.json` 建好第一个 profile(例如正在用 anyrouter key 就建
|
|
43
|
+
`anyrouter`;正在用 ChatGPT 登录就建 `official`),并补上受管标记。密钥只复制到
|
|
44
|
+
本机 `profiles/<名>/auth.json`(权限 600),不会上传。
|
|
45
|
+
|
|
46
|
+
也可手动:`csw init` · 再加中转:`csw add`。
|
|
47
|
+
|
|
39
48
|
---
|
|
40
49
|
|
|
41
50
|
## 命令速查
|
|
@@ -47,6 +56,8 @@ csw list # 验证
|
|
|
47
56
|
| `csw add [名字]` | **向导**:新建一个自定义 API-key provider(见下) |
|
|
48
57
|
| `csw list` | 列出所有 profile,`*` 标当前 |
|
|
49
58
|
| `csw status` | 显示当前 profile + 受管块 + auth 模式 |
|
|
59
|
+
| `csw init` | 无 profile 时从当前 Codex 配置自动初始化(有则只展示) |
|
|
60
|
+
| `csw doctor` | 检查/修复 `config.toml` 受管标记 |
|
|
50
61
|
|
|
51
62
|
> 兼容别名:源码安装后也可使用 `switch` / `codex-switch`(与 `csw` 相同能力)。
|
|
52
63
|
|
package/codex-switch
CHANGED
|
@@ -10,15 +10,19 @@
|
|
|
10
10
|
# Profiles live in ~/.codex/profiles/<name>/ as { provider.env, auth.json }.
|
|
11
11
|
# Provider endpoints are defined once as [model_providers.<name>] in config.toml.
|
|
12
12
|
#
|
|
13
|
-
# Usage:
|
|
14
|
-
# codex-switch
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
13
|
+
# Usage (command alias: csw):
|
|
14
|
+
# csw / codex-switch # interactive picker (via csw) / status (via codex-switch)
|
|
15
|
+
# csw list # list profiles
|
|
16
|
+
# csw status # show current profile + managed block
|
|
17
|
+
# csw doctor # check/repair the managed block (e.g. after Codex rewrote config.toml)
|
|
18
|
+
# csw init # first-run bootstrap if profiles/ is empty
|
|
19
|
+
# csw pick # interactive arrow-key picker
|
|
20
|
+
# csw add [name] # wizard: create a new custom API-key profile
|
|
21
|
+
# csw official # back to ChatGPT login (your original setup)
|
|
22
|
+
# csw <provider> # e.g. anyrouter / a custom profile name
|
|
23
|
+
#
|
|
24
|
+
# First run: if ~/.codex/profiles is empty, auto-snapshots the live config+auth
|
|
25
|
+
# into a profile so switching works immediately (credentials stay local, mode 600).
|
|
22
26
|
set -euo pipefail
|
|
23
27
|
|
|
24
28
|
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
@@ -32,8 +36,103 @@ END='#__CODEX_SWITCH_END__'
|
|
|
32
36
|
die(){ echo "codex-switch: error: $*" >&2; exit 1; }
|
|
33
37
|
current(){ [ -f "$ACTIVE_FILE" ] && cat "$ACTIVE_FILE" || echo official; }
|
|
34
38
|
|
|
39
|
+
# Read a top-level / managed-block string value from config.toml (quoted values).
|
|
40
|
+
# Prefer the managed block when present; ignore keys under [tables].
|
|
41
|
+
_cfg_str(){
|
|
42
|
+
local key="$1"
|
|
43
|
+
if [ -f "$CONFIG" ] && grep -q "$BEGIN" "$CONFIG" 2>/dev/null; then
|
|
44
|
+
awk -v b="$BEGIN" -v e="$END" -v k="$key" '
|
|
45
|
+
$0==b { p=1; next }
|
|
46
|
+
$0==e { p=0; next }
|
|
47
|
+
p && $0 ~ "^[[:space:]]*" k "[[:space:]]*=" {
|
|
48
|
+
if (match($0, /"[^"]*"/)) { print substr($0, RSTART+1, RLENGTH-2); exit }
|
|
49
|
+
}' "$CONFIG"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
[ -f "$CONFIG" ] || return 0
|
|
53
|
+
awk -v k="$key" '
|
|
54
|
+
/^[[:space:]]*\[/ { exit }
|
|
55
|
+
$0 ~ "^[[:space:]]*" k "[[:space:]]*=" {
|
|
56
|
+
if (match($0, /"[^"]*"/)) { print substr($0, RSTART+1, RLENGTH-2); exit }
|
|
57
|
+
}' "$CONFIG"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Count usable profiles (dir + provider.env + auth.json).
|
|
61
|
+
_profile_count(){
|
|
62
|
+
local n=0 d
|
|
63
|
+
for d in "$PROFILES"/*/; do
|
|
64
|
+
[ -d "$d" ] || continue
|
|
65
|
+
[ -f "$d/provider.env" ] && [ -f "$d/auth.json" ] && n=$((n+1))
|
|
66
|
+
done
|
|
67
|
+
echo "$n"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# First-run bootstrap: if no profiles exist, snapshot the live Codex config/auth
|
|
71
|
+
# into ~/.codex/profiles/<name>/ so `csw` works immediately after install.
|
|
72
|
+
ensure_profiles(){
|
|
73
|
+
mkdir -p "$PROFILES"
|
|
74
|
+
[ "$(_profile_count)" -gt 0 ] && return 0
|
|
75
|
+
|
|
76
|
+
echo "codex-switch: no profiles yet — bootstrapping from current Codex setup ..."
|
|
77
|
+
|
|
78
|
+
[ -f "$CONFIG" ] || die "no config at $CONFIG (install/run Codex once first)"
|
|
79
|
+
[ -f "$AUTH" ] || die "no auth.json at $AUTH (log in to Codex or set an API key first)"
|
|
80
|
+
|
|
81
|
+
local model provider mode name
|
|
82
|
+
model="$(_cfg_str model)"
|
|
83
|
+
model="${model:-gpt-5.5}"
|
|
84
|
+
provider="$(_cfg_str model_provider)"
|
|
85
|
+
|
|
86
|
+
# Decide chatgpt vs apikey from live auth (never invent credentials).
|
|
87
|
+
# API-key auth.json has OPENAI_API_KEY; ChatGPT login uses OAuth tokens.
|
|
88
|
+
if grep -q 'OPENAI_API_KEY' "$AUTH" 2>/dev/null; then
|
|
89
|
+
mode=apikey
|
|
90
|
+
else
|
|
91
|
+
mode=chatgpt
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
if [ "$mode" = "apikey" ]; then
|
|
95
|
+
name="${provider:-default}"
|
|
96
|
+
name="$(printf '%s' "$name" | tr -cd 'A-Za-z0-9_-')"
|
|
97
|
+
[ -n "$name" ] || name=default
|
|
98
|
+
mkdir -p "$PROFILES/$name"
|
|
99
|
+
{
|
|
100
|
+
echo "MODE=apikey"
|
|
101
|
+
echo "MODEL=$model"
|
|
102
|
+
echo "PROVIDER=$name"
|
|
103
|
+
} > "$PROFILES/$name/provider.env"
|
|
104
|
+
install -m 600 "$AUTH" "$PROFILES/$name/auth.json"
|
|
105
|
+
printf '%s\n' "$name" > "$ACTIVE_FILE"
|
|
106
|
+
echo " ✓ created profile '$name' (apikey · provider=$name · model=$model)"
|
|
107
|
+
else
|
|
108
|
+
name=official
|
|
109
|
+
mkdir -p "$PROFILES/$name"
|
|
110
|
+
{
|
|
111
|
+
echo "MODE=chatgpt"
|
|
112
|
+
echo "MODEL=$model"
|
|
113
|
+
} > "$PROFILES/$name/provider.env"
|
|
114
|
+
install -m 600 "$AUTH" "$PROFILES/$name/auth.json"
|
|
115
|
+
printf '%s\n' "$name" > "$ACTIVE_FILE"
|
|
116
|
+
echo " ✓ created profile 'official' (ChatGPT login · model=$model)"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
echo " ✓ snapshotted current auth.json → $PROFILES/$name/auth.json (mode 600)"
|
|
120
|
+
echo " credentials never leave this machine"
|
|
121
|
+
|
|
122
|
+
# Managed markers so later switches can rewrite the block safely.
|
|
123
|
+
if ! { grep -q "$BEGIN" "$CONFIG" && grep -q "$END" "$CONFIG"; }; then
|
|
124
|
+
echo " ensuring managed markers in config.toml ..."
|
|
125
|
+
doctor
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
echo " Done. Add more providers anytime: csw add"
|
|
129
|
+
echo
|
|
130
|
+
}
|
|
131
|
+
|
|
35
132
|
list(){
|
|
133
|
+
ensure_profiles
|
|
36
134
|
echo "profiles (current: $(current)):"
|
|
135
|
+
local d n mark info found=0
|
|
37
136
|
for d in "$PROFILES"/*/; do
|
|
38
137
|
[ -d "$d" ] || continue
|
|
39
138
|
n=$(basename "$d")
|
|
@@ -42,7 +141,9 @@ list(){
|
|
|
42
141
|
if [ "${MODE:-}" = chatgpt ]; then echo "ChatGPT login model=$MODEL"
|
|
43
142
|
else echo "apikey provider=$PROVIDER model=$MODEL"; fi )
|
|
44
143
|
printf "%s%-12s %s\n" "$mark" "$n" "$info"
|
|
144
|
+
found=1
|
|
45
145
|
done
|
|
146
|
+
[ "$found" -eq 1 ] || echo " (none — run: csw add)"
|
|
46
147
|
}
|
|
47
148
|
|
|
48
149
|
build_block(){ # $1 = profile dir -> prints the managed config block
|
|
@@ -154,13 +255,27 @@ use(){
|
|
|
154
255
|
}
|
|
155
256
|
|
|
156
257
|
status(){
|
|
258
|
+
ensure_profiles
|
|
157
259
|
echo "active profile : $(current)"
|
|
158
|
-
|
|
159
|
-
|
|
260
|
+
if [ -f "$CONFIG" ] && grep -q "$BEGIN" "$CONFIG" 2>/dev/null; then
|
|
261
|
+
echo "config block :"; awk -v b="$BEGIN" -v e="$END" '$0==b{p=1} p{print} $0==e{p=0}' "$CONFIG" | sed 's/^/ /'
|
|
262
|
+
else
|
|
263
|
+
echo "config block : (no managed markers — run: csw doctor)"
|
|
264
|
+
fi
|
|
265
|
+
if [ ! -f "$AUTH" ]; then
|
|
266
|
+
echo "auth.json : (missing)"
|
|
267
|
+
elif grep -q '"auth_mode"[[:space:]]*:[[:space:]]*"chatgpt"' "$AUTH" 2>/dev/null \
|
|
268
|
+
|| { ! grep -q 'OPENAI_API_KEY' "$AUTH" 2>/dev/null && grep -qE '"tokens"|"access_token"' "$AUTH" 2>/dev/null; }; then
|
|
160
269
|
echo "auth.json : ChatGPT login (OAuth tokens)"
|
|
161
270
|
else
|
|
162
|
-
|
|
163
|
-
|
|
271
|
+
# Match common sk- prefixes; never print the full key (set -u safe).
|
|
272
|
+
local k=""
|
|
273
|
+
k=$(grep -oE 'sk-[A-Za-z0-9]{4,8}' "$AUTH" 2>/dev/null | head -1 || true)
|
|
274
|
+
if [ -n "$k" ]; then
|
|
275
|
+
echo "auth.json : apikey (${k}...redacted)"
|
|
276
|
+
else
|
|
277
|
+
echo "auth.json : apikey"
|
|
278
|
+
fi
|
|
164
279
|
fi
|
|
165
280
|
}
|
|
166
281
|
|
|
@@ -168,10 +283,11 @@ status(){
|
|
|
168
283
|
# keyboard: ↑/↓ j/k move · Enter confirm · 1-9 direct · q/Esc cancel
|
|
169
284
|
# mouse: click to select · wheel to scroll · selection auto-copied to clipboard
|
|
170
285
|
pick(){
|
|
286
|
+
ensure_profiles
|
|
171
287
|
local profiles=() cur sel=0 i d
|
|
172
288
|
cur="$(current)"
|
|
173
289
|
for d in "$PROFILES"/*/; do [ -d "$d" ] || continue; profiles+=("$(basename "$d")"); done
|
|
174
|
-
[ ${#profiles[@]} -gt 0 ] || die "no profiles found in $PROFILES"
|
|
290
|
+
[ ${#profiles[@]} -gt 0 ] || die "no profiles found in $PROFILES (run: csw add)"
|
|
175
291
|
for i in "${!profiles[@]}"; do [ "${profiles[$i]}" = "$cur" ] && sel=$i; done
|
|
176
292
|
local rows=${#profiles[@]}
|
|
177
293
|
|
|
@@ -527,7 +643,7 @@ add(){
|
|
|
527
643
|
fi
|
|
528
644
|
|
|
529
645
|
read -r -p "现在就切到 $name? [y/N] " s
|
|
530
|
-
if [[ "$s" =~ ^[Yy]$ ]]; then echo; use "$name"; else echo "之后用:
|
|
646
|
+
if [[ "$s" =~ ^[Yy]$ ]]; then echo; use "$name"; else echo "之后用: csw $name"; fi
|
|
531
647
|
}
|
|
532
648
|
|
|
533
649
|
cmd="${1:-status}"
|
|
@@ -535,11 +651,15 @@ case "$cmd" in
|
|
|
535
651
|
list|ls) list ;;
|
|
536
652
|
status|st|"") status ;;
|
|
537
653
|
pick|menu|i|-i|p) pick ;;
|
|
538
|
-
add|new) shift; add "${1:-}" ;;
|
|
654
|
+
add|new) ensure_profiles; shift; add "${1:-}" ;;
|
|
539
655
|
doctor|fix) doctor ;;
|
|
540
|
-
|
|
656
|
+
init|bootstrap) # force re-run only when empty; always print state
|
|
657
|
+
if [ "$(_profile_count)" -eq 0 ]; then ensure_profiles
|
|
658
|
+
else echo "profiles already present:"; list; fi ;;
|
|
659
|
+
use) ensure_profiles; shift; use "${1:?profile name required}" ;;
|
|
541
660
|
-h|--help|help) sed -n '2,32p' "$0" ;;
|
|
542
|
-
*)
|
|
661
|
+
*) ensure_profiles
|
|
662
|
+
if [ -d "$PROFILES/$cmd" ]; then use "$cmd"
|
|
543
663
|
else echo "unknown: $cmd"; echo; list
|
|
544
|
-
echo; echo "usage:
|
|
664
|
+
echo; echo "usage: csw [pick|add|list|status|doctor|init|<profile>]"; exit 1; fi ;;
|
|
545
665
|
esac
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-switch-helper",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Codex-Switch-Helper: one-command switch between official ChatGPT login and third-party API-key providers for OpenAI Codex (zero deps, pure bash)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"csw": "./csw"
|