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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/codex-switch +139 -19
  3. 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 # show current status
15
- # codex-switch list # list profiles
16
- # codex-switch doctor # check/repair the managed block (e.g. after Codex rewrote config.toml)
17
- # codex-switch pick # interactive arrow-key picker (also: `switch`)
18
- # codex-switch add [name] # wizard: create a new custom API-key profile
19
- # codex-switch official # back to ChatGPT login (your original setup)
20
- # codex-switch anyrouter # custom key via anyrouter.top
21
- # codex-switch muyuan # custom key via muyuan.do (free/temp)
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
- echo "config block :"; awk -v b="$BEGIN" -v e="$END" '$0==b{p=1} p{print} $0==e{p=0}' "$CONFIG" | sed 's/^/ /'
159
- if grep -q '"auth_mode"[[:space:]]*:[[:space:]]*"chatgpt"' "$AUTH" 2>/dev/null; then
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
- local k; k=$(grep -o 'sk-[A-Za-z0-9]\{6\}' "$AUTH" 2>/dev/null | head -1)
163
- echo "auth.json : apikey ${k:+($k…redacted)}"
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 "之后用: switch $name"; fi
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
- use) shift; use "${1:?profile name required}" ;;
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
- *) if [ -d "$PROFILES/$cmd" ]; then use "$cmd"
661
+ *) ensure_profiles
662
+ if [ -d "$PROFILES/$cmd" ]; then use "$cmd"
543
663
  else echo "unknown: $cmd"; echo; list
544
- echo; echo "usage: codex-switch [pick|add|list|status|doctor|<profile>]"; exit 1; fi ;;
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.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"