@wangxt0223/codex-switcher 0.5.1 → 0.6.0

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.
@@ -2,7 +2,12 @@
2
2
  set -euo pipefail
3
3
 
4
4
  STATE_DIR="${CODEX_SWITCHER_STATE_DIR:-$HOME/.codex-switcher}"
5
- PROFILES_DIR="${CODEX_SWITCHER_PROFILES_DIR:-$HOME/.codex-profiles}"
5
+ ENVS_DIR="${CODEX_SWITCHER_ENVS_DIR:-$HOME/.codex-envs}"
6
+ ACCOUNTS_DIR="${CODEX_SWITCHER_ACCOUNTS_DIR:-$STATE_DIR/env-accounts}"
7
+ DEFAULT_HOME="${CODEX_SWITCHER_DEFAULT_HOME:-$HOME/.codex}"
8
+ DEFAULT_ENV_NAME="default"
9
+ DEFAULT_ACCOUNT_NAME="default"
10
+
6
11
  APP_LOG="${CODEX_SWITCHER_APP_LOG:-$STATE_DIR/app.log}"
7
12
  SWITCH_LOG="${CODEX_SWITCHER_SWITCH_LOG:-$STATE_DIR/switcher.log}"
8
13
  APP_PID_FILE="$STATE_DIR/app.pid"
@@ -14,23 +19,37 @@ SCRIPT_NAME="${CODEX_SWITCHER_INVOKED_AS:-$(basename "$0")}"
14
19
  usage() {
15
20
  cat <<'USAGE'
16
21
  Usage:
17
- codex-sw add <profile>
18
- codex-sw remove <profile> [--force]
22
+ codex-sw env list
23
+ codex-sw env create <env> [--empty|--from-default|--from-env <src>]
24
+ codex-sw env use <env> [--target cli|app|both]
25
+ codex-sw env remove <env> [--force]
26
+ codex-sw env current [cli|app]
27
+ codex-sw env path [env]
28
+
29
+ codex-sw account list [--env <env>]
30
+ codex-sw account add <account> [--env <env>]
31
+ codex-sw account remove <account> [--env <env>] [--force]
32
+ codex-sw account login <account> [--env <env>] [--target cli|app|both] [--sync|--no-sync]
33
+ codex-sw account use <account> [--env <env>] [--target cli|app|both] [--sync|--no-sync]
34
+ codex-sw account logout [account] [--env <env>] [--target cli|app|both]
35
+ codex-sw account current [cli|app]
36
+
19
37
  codex-sw list
20
- codex-sw import-default <profile> [--with-auth] [--force]
21
- codex-sw use <profile> [--sync|--no-sync] [--launch|--no-launch] [-- <codex args...>]
22
- codex-sw switch <profile> [--sync|--no-sync] [--launch|--no-launch] [-- <codex args...>]
23
- codex-sw current [cli|app]
24
38
  codex-sw status
25
-
39
+ codex-sw current [cli|app]
26
40
  codex-sw exec -- <codex args...>
27
- codex-sw login [profile] [--sync|--no-sync]
28
- codex-sw logout [profile]
29
- codex-sw env [profile]
30
-
31
- codex-sw app open [profile] [-- <app args...>]
32
- codex-sw app use <profile> [-- <app args...>]
33
- codex-sw app logout [profile]
41
+ codex-sw login [account] [--sync|--no-sync]
42
+ codex-sw logout [account]
43
+
44
+ codex-sw use <account> [--sync|--no-sync] [--launch|--no-launch] [-- <codex args...>]
45
+ codex-sw switch <account> [--sync|--no-sync] [--launch|--no-launch] [-- <codex args...>]
46
+ codex-sw add <account>
47
+ codex-sw remove <account> [--force]
48
+ codex-sw import-default <env> [--with-auth] [--force]
49
+
50
+ codex-sw app open [account] [-- <app args...>]
51
+ codex-sw app use <account> [-- <app args...>]
52
+ codex-sw app logout [account]
34
53
  codex-sw app status
35
54
  codex-sw app stop
36
55
  codex-sw app current
@@ -44,22 +63,11 @@ Usage:
44
63
  Compatibility:
45
64
  codex-switcher <same-commands>
46
65
 
47
- Sync behavior:
48
- --sync enable overwrite sync (excluding auth.json)
49
- - login --sync: ~/.codex -> target profile
50
- - use/switch --sync: current CLI profile -> target profile
51
- --no-sync disable sync explicitly (default)
52
-
53
- Environment overrides:
54
- CODEX_SWITCHER_STATE_DIR
55
- CODEX_SWITCHER_PROFILES_DIR
56
- CODEX_SWITCHER_APP_BIN
57
- CODEX_SWITCHER_APP_LOG
58
- CODEX_SWITCHER_SWITCH_LOG
59
- CODEX_SWITCHER_LOCK_WAIT_SECONDS
60
- CODEX_SWITCHER_DEFAULT_HOME
61
- CODEX_SWITCHER_UPGRADE_REGISTRY
62
- CODEX_SWITCHER_NPM_PACKAGE
66
+ Notes:
67
+ - Built-in env "default" always maps to ~/.codex (or CODEX_SWITCHER_DEFAULT_HOME).
68
+ - Same-env account switch only swaps auth.json and does not sync shared data.
69
+ - `list` prints: EMAIL / PLAN / 5H USAGE / WEEKLY USAGE / LAST ACTIVITY (+ env/account context).
70
+ - Usage metrics are API-first and automatically fallback to local sessions; source is shown as (api|local).
63
71
  USAGE
64
72
  }
65
73
 
@@ -104,8 +112,9 @@ log_event() {
104
112
  }
105
113
 
106
114
  ensure_dirs() {
107
- mkdir -p "$STATE_DIR" "$PROFILES_DIR"
108
- chmod 700 "$STATE_DIR" "$PROFILES_DIR" 2>/dev/null || true
115
+ mkdir -p "$STATE_DIR" "$ENVS_DIR" "$ACCOUNTS_DIR"
116
+ mkdir -p "$DEFAULT_HOME"
117
+ chmod 700 "$STATE_DIR" "$ENVS_DIR" "$ACCOUNTS_DIR" "$DEFAULT_HOME" 2>/dev/null || true
109
118
  }
110
119
 
111
120
  perm_of() {
@@ -117,77 +126,100 @@ perm_of() {
117
126
  fi
118
127
  }
119
128
 
120
- validate_profile_name_noexit() {
129
+ validate_name_noexit() {
121
130
  local name="${1:-}"
122
131
  [[ -n "$name" ]] || return 1
123
132
  [[ "$name" =~ ^[A-Za-z0-9._-]+$ ]] || return 1
124
133
  return 0
125
134
  }
126
135
 
127
- validate_profile_name() {
136
+ validate_env_name_noexit() {
128
137
  local name="${1:-}"
129
- validate_profile_name_noexit "$name" || err "invalid profile '$name' (allowed: A-Z a-z 0-9 . _ -)"
138
+ [[ "$name" == "$DEFAULT_ENV_NAME" ]] && return 0
139
+ validate_name_noexit "$name"
130
140
  }
131
141
 
132
- default_profile_for() {
133
- local target="$1"
134
- if [[ "$target" == "cli" ]]; then
135
- echo "default"
142
+ validate_env_name() {
143
+ local name="${1:-}"
144
+ validate_env_name_noexit "$name" || err "invalid env '$name' (allowed: A-Z a-z 0-9 . _ -, plus reserved: default)"
145
+ }
146
+
147
+ validate_account_name() {
148
+ local name="${1:-}"
149
+ validate_name_noexit "$name" || err "invalid account '$name' (allowed: A-Z a-z 0-9 . _ -)"
150
+ }
151
+
152
+ env_root_path() {
153
+ local env="$1"
154
+ echo "$ENVS_DIR/$env"
155
+ }
156
+
157
+ env_home_path() {
158
+ local env="$1"
159
+ if [[ "$env" == "$DEFAULT_ENV_NAME" ]]; then
160
+ echo "$DEFAULT_HOME"
136
161
  else
137
- echo "app-default"
162
+ echo "$ENVS_DIR/$env/home"
138
163
  fi
139
164
  }
140
165
 
141
- profile_path() {
142
- local name="$1"
143
- echo "$PROFILES_DIR/$name"
166
+ account_dir_path() {
167
+ local env="$1"
168
+ local account="$2"
169
+ echo "$ACCOUNTS_DIR/$env/$account"
170
+ }
171
+
172
+ account_auth_path() {
173
+ local env="$1"
174
+ local account="$2"
175
+ echo "$(account_dir_path "$env" "$account")/auth.json"
144
176
  }
145
177
 
146
- ensure_profile() {
147
- local name="$1"
148
- local p
149
- p="$(profile_path "$name")"
150
- mkdir -p "$p"
151
- chmod 700 "$p" 2>/dev/null || true
178
+ current_env_file() {
179
+ local target="$1"
180
+ echo "$STATE_DIR/current_${target}_env"
152
181
  }
153
182
 
154
- require_profile_exists() {
155
- local name="$1"
156
- local p
157
- p="$(profile_path "$name")"
158
- [[ -d "$p" ]] || err "profile '$name' not found. run: $SCRIPT_NAME add $name"
183
+ current_account_file() {
184
+ local target="$1"
185
+ echo "$STATE_DIR/current_${target}_account"
159
186
  }
160
187
 
161
- current_file() {
188
+ set_current_env() {
162
189
  local target="$1"
163
- echo "$STATE_DIR/current_${target}"
190
+ local env="$2"
191
+ local file tmp
192
+ file="$(current_env_file "$target")"
193
+ tmp="${file}.tmp"
194
+ printf '%s\n' "$env" > "$tmp"
195
+ chmod 600 "$tmp" 2>/dev/null || true
196
+ mv "$tmp" "$file"
164
197
  }
165
198
 
166
- set_current() {
199
+ set_current_account() {
167
200
  local target="$1"
168
- local profile="$2"
201
+ local account="$2"
169
202
  local file tmp
170
- file="$(current_file "$target")"
203
+ file="$(current_account_file "$target")"
171
204
  tmp="${file}.tmp"
172
- printf '%s\n' "$profile" > "$tmp"
205
+ printf '%s\n' "$account" > "$tmp"
173
206
  chmod 600 "$tmp" 2>/dev/null || true
174
207
  mv "$tmp" "$file"
175
208
  }
176
209
 
177
- read_current() {
210
+ read_current_env() {
178
211
  local target="$1"
179
- local file value default
180
- file="$(current_file "$target")"
181
- default="$(default_profile_for "$target")"
212
+ local file value
213
+ file="$(current_env_file "$target")"
182
214
 
183
215
  if [[ ! -f "$file" ]]; then
184
- echo "$default"
216
+ echo "$DEFAULT_ENV_NAME"
185
217
  return 1
186
218
  fi
187
219
 
188
220
  value="$(head -n 1 "$file" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
189
- if ! validate_profile_name_noexit "$value"; then
190
- echo "$default"
221
+ if ! validate_env_name_noexit "$value"; then
222
+ echo "$DEFAULT_ENV_NAME"
191
223
  return 2
192
224
  fi
193
225
 
@@ -195,32 +227,170 @@ read_current() {
195
227
  return 0
196
228
  }
197
229
 
198
- first_profile_name() {
199
- local first
200
- first="$(find "$PROFILES_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort | head -n 1)"
201
- if [[ -n "$first" ]]; then
202
- echo "$first"
230
+ read_current_account() {
231
+ local target="$1"
232
+ local file value
233
+ file="$(current_account_file "$target")"
234
+
235
+ if [[ ! -f "$file" ]]; then
236
+ echo "$DEFAULT_ACCOUNT_NAME"
237
+ return 1
238
+ fi
239
+
240
+ value="$(head -n 1 "$file" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
241
+ if ! validate_name_noexit "$value"; then
242
+ echo "$DEFAULT_ACCOUNT_NAME"
243
+ return 2
244
+ fi
245
+
246
+ echo "$value"
247
+ return 0
248
+ }
249
+
250
+ ensure_env_home() {
251
+ local env="$1"
252
+ local home
253
+ validate_env_name "$env"
254
+ home="$(env_home_path "$env")"
255
+ mkdir -p "$home"
256
+ if [[ "$env" != "$DEFAULT_ENV_NAME" ]]; then
257
+ mkdir -p "$(env_root_path "$env")"
258
+ fi
259
+ chmod 700 "$home" 2>/dev/null || true
260
+ }
261
+
262
+ env_exists() {
263
+ local env="$1"
264
+ if [[ "$env" == "$DEFAULT_ENV_NAME" ]]; then
265
+ return 0
266
+ fi
267
+ [[ -d "$(env_home_path "$env")" ]]
268
+ }
269
+
270
+ require_env_exists() {
271
+ local env="$1"
272
+ validate_env_name "$env"
273
+ if [[ "$env" == "$DEFAULT_ENV_NAME" ]]; then
274
+ ensure_env_home "$env"
203
275
  return 0
204
276
  fi
277
+ [[ -d "$(env_home_path "$env")" ]] || err "env '$env' not found. run: $SCRIPT_NAME env create $env"
278
+ }
279
+
280
+ ensure_account_slot() {
281
+ local env="$1"
282
+ local account="$2"
283
+ validate_env_name "$env"
284
+ validate_account_name "$account"
285
+ mkdir -p "$(account_dir_path "$env" "$account")"
286
+ chmod 700 "$(account_dir_path "$env" "$account")" 2>/dev/null || true
287
+ }
288
+
289
+ account_has_auth() {
290
+ local env="$1"
291
+ local account="$2"
292
+ local auth_file home_auth
293
+ auth_file="$(account_auth_path "$env" "$account")"
294
+ if [[ -f "$auth_file" ]]; then
295
+ return 0
296
+ fi
297
+ if [[ "$account" == "$DEFAULT_ACCOUNT_NAME" ]]; then
298
+ home_auth="$(env_home_path "$env")/auth.json"
299
+ [[ -f "$home_auth" ]] && return 0
300
+ fi
205
301
  return 1
206
302
  }
207
303
 
208
- resolve_app_bin() {
209
- if [[ -n "${CODEX_SWITCHER_APP_BIN:-}" ]]; then
210
- [[ -x "$CODEX_SWITCHER_APP_BIN" ]] || err "CODEX_SWITCHER_APP_BIN is not executable: $CODEX_SWITCHER_APP_BIN"
211
- echo "$CODEX_SWITCHER_APP_BIN"
212
- return
304
+ list_env_names() {
305
+ echo "$DEFAULT_ENV_NAME"
306
+ if [[ -d "$ENVS_DIR" ]]; then
307
+ find "$ENVS_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort | while IFS= read -r env; do
308
+ validate_env_name_noexit "$env" || continue
309
+ [[ "$env" == "$DEFAULT_ENV_NAME" ]] && continue
310
+ [[ -d "$(env_home_path "$env")" ]] || continue
311
+ echo "$env"
312
+ done
213
313
  fi
314
+ }
214
315
 
215
- if [[ -x "/Applications/Codex.app/Contents/MacOS/Codex" ]]; then
216
- echo "/Applications/Codex.app/Contents/MacOS/Codex"
217
- return
316
+ list_accounts_for_env() {
317
+ local env="$1"
318
+ local dir auth_file any
319
+ dir="$ACCOUNTS_DIR/$env"
320
+ any=0
321
+
322
+ if [[ -d "$dir" ]]; then
323
+ while IFS= read -r account; do
324
+ validate_name_noexit "$account" || continue
325
+ auth_file="$(account_auth_path "$env" "$account")"
326
+ if [[ -f "$auth_file" ]]; then
327
+ any=1
328
+ echo "$account"
329
+ fi
330
+ done < <(find "$dir" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)
218
331
  fi
219
- if [[ -x "$HOME/Applications/Codex.app/Contents/MacOS/Codex" ]]; then
220
- echo "$HOME/Applications/Codex.app/Contents/MacOS/Codex"
221
- return
332
+
333
+ if [[ "$any" -eq 0 ]] && [[ "$env" == "$DEFAULT_ENV_NAME" ]] && [[ -f "$(env_home_path "$env")/auth.json" ]]; then
334
+ echo "$DEFAULT_ACCOUNT_NAME"
222
335
  fi
223
- err "Codex.app binary not found. set CODEX_SWITCHER_APP_BIN manually"
336
+ }
337
+
338
+ copy_auth_file() {
339
+ local src="$1"
340
+ local dst="$2"
341
+ local tmp
342
+ tmp="${dst}.tmp"
343
+ cp "$src" "$tmp"
344
+ chmod 600 "$tmp" 2>/dev/null || true
345
+ mv "$tmp" "$dst"
346
+ }
347
+
348
+ save_env_auth_to_account() {
349
+ local env="$1"
350
+ local account="$2"
351
+ local home_auth dst
352
+ home_auth="$(env_home_path "$env")/auth.json"
353
+ [[ -f "$home_auth" ]] || return 1
354
+ ensure_account_slot "$env" "$account"
355
+ dst="$(account_auth_path "$env" "$account")"
356
+ copy_auth_file "$home_auth" "$dst"
357
+ }
358
+
359
+ clear_env_auth() {
360
+ local env="$1"
361
+ rm -f -- "$(env_home_path "$env")/auth.json"
362
+ }
363
+
364
+ install_account_auth_to_env() {
365
+ local env="$1"
366
+ local account="$2"
367
+ local src dst
368
+ src="$(account_auth_path "$env" "$account")"
369
+ dst="$(env_home_path "$env")/auth.json"
370
+ if [[ -f "$src" ]]; then
371
+ copy_auth_file "$src" "$dst"
372
+ return 0
373
+ fi
374
+ if [[ "$account" == "$DEFAULT_ACCOUNT_NAME" ]] && [[ -f "$dst" ]]; then
375
+ return 0
376
+ fi
377
+ rm -f -- "$dst"
378
+ }
379
+
380
+ sync_overwrite_excluding_auth() {
381
+ local src="$1"
382
+ local dst="$2"
383
+
384
+ [[ -d "$src" ]] || err "sync source not found: $src"
385
+ mkdir -p "$dst"
386
+
387
+ if command -v rsync >/dev/null 2>&1; then
388
+ rsync -a --checksum --delete --exclude 'auth.json' "$src/" "$dst/"
389
+ else
390
+ find "$dst" -mindepth 1 -maxdepth 1 ! -name 'auth.json' -exec rm -rf -- {} + 2>/dev/null || true
391
+ (cd "$src" && tar cf - --exclude ./auth.json .) | (cd "$dst" && tar xpf -)
392
+ fi
393
+ chmod 700 "$dst" 2>/dev/null || true
224
394
  }
225
395
 
226
396
  acquire_lock() {
@@ -271,24 +441,60 @@ with_lock() {
271
441
  trap - EXIT INT TERM
272
442
  }
273
443
 
274
- run_codex_for_profile() {
275
- local profile="$1"
444
+ with_targets() {
445
+ local target="$1"
446
+ shift
447
+ local fn="$1"
276
448
  shift
277
- require_profile_exists "$profile"
278
- CODEX_HOME="$(profile_path "$profile")" command codex "$@"
449
+ case "$target" in
450
+ cli|app)
451
+ "$fn" "$target" "$@"
452
+ ;;
453
+ both)
454
+ "$fn" cli "$@"
455
+ "$fn" app "$@"
456
+ ;;
457
+ *)
458
+ err "invalid target '$target' (use cli|app|both)"
459
+ ;;
460
+ esac
279
461
  }
280
462
 
281
- login_state_for_profile() {
282
- local profile="$1"
283
- local p rc
284
- p="$(profile_path "$profile")"
285
- if [[ ! -d "$p" ]]; then
286
- echo "missing-profile"
287
- return
463
+ target_home_path() {
464
+ local target="$1"
465
+ local env
466
+ env="$(read_current_env "$target" || true)"
467
+ env_home_path "$env"
468
+ }
469
+
470
+ ensure_target_auth_installed() {
471
+ local target="$1"
472
+ local env account
473
+ env="$(read_current_env "$target" || true)"
474
+ account="$(read_current_account "$target" || true)"
475
+ ensure_env_home "$env"
476
+ if account_has_auth "$env" "$account"; then
477
+ install_account_auth_to_env "$env" "$account"
478
+ else
479
+ clear_env_auth "$env"
288
480
  fi
481
+ }
482
+
483
+ run_codex_for_target() {
484
+ local target="$1"
485
+ shift
486
+ ensure_target_auth_installed "$target"
487
+ CODEX_HOME="$(target_home_path "$target")" command codex "$@"
488
+ }
489
+
490
+ login_state_for_target() {
491
+ local target="$1"
492
+ local rc home
493
+ ensure_target_auth_installed "$target"
494
+ home="$(target_home_path "$target")"
289
495
 
290
496
  rc=0
291
- CODEX_HOME="$p" command codex login status >/dev/null 2>&1 || rc=$?
497
+ CODEX_HOME="$home" command codex login status >/dev/null 2>&1 || rc=$?
292
498
  if [[ "$rc" -eq 0 ]]; then
293
499
  echo "logged-in"
294
500
  else
@@ -296,6 +502,24 @@ login_state_for_profile() {
296
502
  fi
297
503
  }
298
504
 
505
+ resolve_app_bin() {
506
+ if [[ -n "${CODEX_SWITCHER_APP_BIN:-}" ]]; then
507
+ [[ -x "$CODEX_SWITCHER_APP_BIN" ]] || err "CODEX_SWITCHER_APP_BIN is not executable: $CODEX_SWITCHER_APP_BIN"
508
+ echo "$CODEX_SWITCHER_APP_BIN"
509
+ return
510
+ fi
511
+
512
+ if [[ -x "/Applications/Codex.app/Contents/MacOS/Codex" ]]; then
513
+ echo "/Applications/Codex.app/Contents/MacOS/Codex"
514
+ return
515
+ fi
516
+ if [[ -x "$HOME/Applications/Codex.app/Contents/MacOS/Codex" ]]; then
517
+ echo "$HOME/Applications/Codex.app/Contents/MacOS/Codex"
518
+ return
519
+ fi
520
+ err "Codex.app binary not found. set CODEX_SWITCHER_APP_BIN manually"
521
+ }
522
+
299
523
  kill_tree() {
300
524
  local pid="$1"
301
525
  local child
@@ -342,194 +566,461 @@ app_stop_managed() {
342
566
  echo "Stopped managed app process"
343
567
  }
344
568
 
345
- prompt_confirm_remove() {
346
- local profile="$1"
347
- if [[ ! -t 0 ]]; then
348
- err "non-interactive terminal; rerun with --force"
569
+ metrics_script_path() {
570
+ local script_dir
571
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
572
+ echo "$script_dir/profile-metrics.py"
573
+ }
574
+
575
+ collect_profile_metrics_tsv() {
576
+ local account="$1"
577
+ local auth_file="$2"
578
+ local data_path="$3"
579
+ local script_path
580
+ script_path="$(metrics_script_path)"
581
+ [[ -f "$script_path" ]] || return 1
582
+ python3 "$script_path" --account-name "$account" --auth-file "$auth_file" --data-path "$data_path" 2>/dev/null
583
+ }
584
+
585
+ cmd_env_list() {
586
+ local cli_env app_env env marks
587
+ cli_env="$(read_current_env cli || true)"
588
+ app_env="$(read_current_env app || true)"
589
+ list_env_names | sort -u | while IFS= read -r env; do
590
+ marks=""
591
+ [[ "$env" == "$cli_env" ]] && marks="${marks} [cli-current]"
592
+ [[ "$env" == "$app_env" ]] && marks="${marks} [app-current]"
593
+ echo "- $env$marks"
594
+ done
595
+ }
596
+
597
+ cmd_env_create() {
598
+ local env="$1"
599
+ local mode="$2"
600
+ local src_env="${3:-}"
601
+ local home src
602
+
603
+ validate_env_name "$env"
604
+ [[ "$env" != "$DEFAULT_ENV_NAME" ]] || err "cannot create reserved env '$DEFAULT_ENV_NAME'"
605
+ if env_exists "$env"; then
606
+ err "env '$env' already exists"
349
607
  fi
350
- local answer
351
- printf "Remove profile '%s'? This deletes %s [y/N]: " "$profile" "$(profile_path "$profile")" >&2
352
- read -r answer
353
- case "$answer" in
354
- y|Y|yes|YES)
355
- return 0
608
+
609
+ home="$(env_home_path "$env")"
610
+ mkdir -p "$home"
611
+ chmod 700 "$home" 2>/dev/null || true
612
+
613
+ case "$mode" in
614
+ empty)
615
+ :
616
+ ;;
617
+ from-default)
618
+ sync_overwrite_excluding_auth "$DEFAULT_HOME" "$home"
619
+ ;;
620
+ from-env)
621
+ validate_env_name "$src_env"
622
+ require_env_exists "$src_env"
623
+ src="$(env_home_path "$src_env")"
624
+ sync_overwrite_excluding_auth "$src" "$home"
356
625
  ;;
357
626
  *)
358
- echo "Cancelled"
359
- return 1
627
+ err "unknown env create mode: $mode"
360
628
  ;;
361
629
  esac
630
+
631
+ log_event INFO "env_create env=$env mode=$mode src=$src_env"
632
+ echo "Created env: $env"
362
633
  }
363
634
 
364
- cmd_add() {
365
- local profile="$1"
366
- validate_profile_name "$profile"
367
- ensure_profile "$profile"
368
- log_event INFO "profile_add profile=$profile"
369
- echo "Added profile: $profile"
635
+ _cmd_env_use_target() {
636
+ local target="$1"
637
+ local env="$2"
638
+ local account
639
+
640
+ account="$(read_current_account "$target" || true)"
641
+ set_current_env "$target" "$env"
642
+ if account_has_auth "$env" "$account"; then
643
+ set_current_account "$target" "$account"
644
+ install_account_auth_to_env "$env" "$account"
645
+ else
646
+ set_current_account "$target" "$DEFAULT_ACCOUNT_NAME"
647
+ if account_has_auth "$env" "$DEFAULT_ACCOUNT_NAME"; then
648
+ install_account_auth_to_env "$env" "$DEFAULT_ACCOUNT_NAME"
649
+ else
650
+ clear_env_auth "$env"
651
+ fi
652
+ fi
370
653
  }
371
654
 
372
- cmd_remove() {
373
- local profile="$1"
374
- local force="${2:-false}"
375
- local cli_cur app_cur
655
+ cmd_env_use() {
656
+ local env="$1"
657
+ local target="$2"
658
+
659
+ require_env_exists "$env"
660
+ with_targets "$target" _cmd_env_use_target "$env"
661
+ log_event INFO "env_use env=$env target=$target"
662
+ echo "Switched $target env to: $env"
663
+ }
376
664
 
377
- validate_profile_name "$profile"
378
- require_profile_exists "$profile"
665
+ cmd_env_remove() {
666
+ local env="$1"
667
+ local force="$2"
668
+ local cli_env app_env
379
669
 
380
- cli_cur="$(read_current cli || true)"
381
- app_cur="$(read_current app || true)"
670
+ validate_env_name "$env"
671
+ [[ "$env" != "$DEFAULT_ENV_NAME" ]] || err "cannot remove reserved env '$DEFAULT_ENV_NAME'"
672
+ env_exists "$env" || err "env '$env' not found"
382
673
 
383
- if [[ "$profile" == "$cli_cur" && "$force" != "true" ]]; then
384
- err "profile '$profile' is current CLI profile; use --force to remove"
385
- fi
386
- if [[ "$profile" == "$app_cur" && "$force" != "true" ]]; then
387
- err "profile '$profile' is current App profile; use --force to remove"
388
- fi
674
+ cli_env="$(read_current_env cli || true)"
675
+ app_env="$(read_current_env app || true)"
389
676
 
390
- if [[ "$force" != "true" ]]; then
391
- prompt_confirm_remove "$profile" || return 0
677
+ if [[ "$env" == "$cli_env" && "$force" != "true" ]]; then
678
+ err "env '$env' is current CLI env; use --force to remove"
679
+ fi
680
+ if [[ "$env" == "$app_env" && "$force" != "true" ]]; then
681
+ err "env '$env' is current App env; use --force to remove"
392
682
  fi
393
683
 
394
- if [[ "$profile" == "$app_cur" ]]; then
684
+ if [[ "$env" == "$app_env" ]]; then
395
685
  app_stop_managed >/dev/null
396
- set_current app "$(default_profile_for app)"
686
+ set_current_env app "$DEFAULT_ENV_NAME"
687
+ set_current_account app "$DEFAULT_ACCOUNT_NAME"
688
+ ensure_target_auth_installed app
397
689
  fi
398
- if [[ "$profile" == "$cli_cur" ]]; then
399
- set_current cli "$(default_profile_for cli)"
690
+ if [[ "$env" == "$cli_env" ]]; then
691
+ set_current_env cli "$DEFAULT_ENV_NAME"
692
+ set_current_account cli "$DEFAULT_ACCOUNT_NAME"
693
+ ensure_target_auth_installed cli
400
694
  fi
401
695
 
402
- rm -rf -- "$(profile_path "$profile")"
403
- log_event INFO "profile_remove profile=$profile force=$force"
404
- echo "Removed profile: $profile"
696
+ rm -rf -- "$(env_root_path "$env")" "$ACCOUNTS_DIR/$env"
697
+ log_event INFO "env_remove env=$env force=$force"
698
+ echo "Removed env: $env"
405
699
  }
406
700
 
407
- cmd_list() {
408
- local cli_cur app_cur
409
- cli_cur="$(read_current cli || true)"
410
- app_cur="$(read_current app || true)"
701
+ cmd_env_current() {
702
+ local target="${1:-all}"
703
+ case "$target" in
704
+ cli)
705
+ read_current_env cli || true
706
+ ;;
707
+ app)
708
+ read_current_env app || true
709
+ ;;
710
+ all)
711
+ echo "cli: $(read_current_env cli || true)"
712
+ echo "app: $(read_current_env app || true)"
713
+ ;;
714
+ *)
715
+ err "invalid target '$target' (use cli|app)"
716
+ ;;
717
+ esac
718
+ }
411
719
 
412
- if ! find "$PROFILES_DIR" -mindepth 1 -maxdepth 1 -type d | grep -q .; then
413
- echo "No profiles yet. Use: $SCRIPT_NAME add <profile>"
414
- return 0
415
- fi
720
+ cmd_env_path() {
721
+ local env="${1:-$(read_current_env cli || true)}"
722
+ validate_env_name "$env"
723
+ require_env_exists "$env"
724
+ echo "export CODEX_HOME='$(env_home_path "$env")'"
725
+ }
416
726
 
417
- find "$PROFILES_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort | while IFS= read -r name; do
418
- local marks
419
- marks=""
420
- [[ "$name" == "$cli_cur" ]] && marks="${marks} [cli-current]"
421
- [[ "$name" == "$app_cur" ]] && marks="${marks} [app-current]"
422
- echo "- $name$marks"
423
- done
727
+ cmd_account_add() {
728
+ local account="$1"
729
+ local env="$2"
730
+ validate_account_name "$account"
731
+ require_env_exists "$env"
732
+ ensure_account_slot "$env" "$account"
733
+ log_event INFO "account_add env=$env account=$account"
734
+ echo "Added account slot: $env/$account"
424
735
  }
425
736
 
426
- cmd_import_default() {
427
- local profile="$1"
428
- local with_auth="$2"
737
+ cmd_account_remove() {
738
+ local account="$1"
739
+ local env="$2"
429
740
  local force="$3"
430
- local src dst
741
+ local cli_env app_env cli_account app_account
431
742
 
432
- src="${CODEX_SWITCHER_DEFAULT_HOME:-$HOME/.codex}"
433
- validate_profile_name "$profile"
434
- [[ -d "$src" ]] || err "default codex home not found: $src"
435
- ensure_profile "$profile"
436
- dst="$(profile_path "$profile")"
743
+ validate_account_name "$account"
744
+ validate_env_name "$env"
437
745
 
438
- if [[ "$force" == "true" ]]; then
439
- find "$dst" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + 2>/dev/null || true
440
- else
441
- if find "$dst" -mindepth 1 -maxdepth 1 | grep -q .; then
442
- err "profile '$profile' is not empty. use --force to overwrite"
443
- fi
746
+ if [[ ! -d "$(account_dir_path "$env" "$account")" ]] && ! account_has_auth "$env" "$account"; then
747
+ err "account '$account' not found in env '$env'"
444
748
  fi
445
749
 
446
- if command -v rsync >/dev/null 2>&1; then
447
- if [[ "$with_auth" == "true" ]]; then
448
- rsync -a "$src/" "$dst/"
449
- else
450
- rsync -a --exclude 'auth.json' "$src/" "$dst/"
451
- fi
452
- else
453
- if [[ "$with_auth" == "true" ]]; then
454
- (cd "$src" && tar cf - .) | (cd "$dst" && tar xpf -)
455
- else
456
- (cd "$src" && tar cf - --exclude ./auth.json .) | (cd "$dst" && tar xpf -)
457
- fi
750
+ cli_env="$(read_current_env cli || true)"
751
+ app_env="$(read_current_env app || true)"
752
+ cli_account="$(read_current_account cli || true)"
753
+ app_account="$(read_current_account app || true)"
754
+
755
+ if [[ "$env" == "$cli_env" && "$account" == "$cli_account" && "$force" != "true" ]]; then
756
+ err "account '$account' is current CLI account in env '$env'; use --force to remove"
757
+ fi
758
+ if [[ "$env" == "$app_env" && "$account" == "$app_account" && "$force" != "true" ]]; then
759
+ err "account '$account' is current App account in env '$env'; use --force to remove"
458
760
  fi
459
761
 
460
- chmod 700 "$dst" 2>/dev/null || true
461
- log_event INFO "import_default profile=$profile src=$src with_auth=$with_auth force=$force"
462
- echo "Imported default data to profile: $profile"
463
- if [[ "$with_auth" != "true" ]]; then
464
- echo "Auth not imported. Run: $SCRIPT_NAME login $profile"
762
+ rm -rf -- "$(account_dir_path "$env" "$account")"
763
+ if [[ "$account" == "$DEFAULT_ACCOUNT_NAME" ]]; then
764
+ rm -f -- "$(env_home_path "$env")/auth.json"
765
+ fi
766
+
767
+ if [[ "$env" == "$cli_env" && "$account" == "$cli_account" ]]; then
768
+ set_current_account cli "$DEFAULT_ACCOUNT_NAME"
769
+ ensure_target_auth_installed cli
465
770
  fi
771
+ if [[ "$env" == "$app_env" && "$account" == "$app_account" ]]; then
772
+ set_current_account app "$DEFAULT_ACCOUNT_NAME"
773
+ ensure_target_auth_installed app
774
+ fi
775
+
776
+ log_event INFO "account_remove env=$env account=$account force=$force"
777
+ echo "Removed account slot: $env/$account"
466
778
  }
467
779
 
468
- sync_overwrite_excluding_auth() {
469
- local src="$1"
470
- local dst="$2"
780
+ cmd_account_list() {
781
+ local env="$1"
782
+ local cli_env app_env cli_account app_account found account marks
783
+ require_env_exists "$env"
471
784
 
472
- [[ -d "$src" ]] || err "sync source not found: $src"
473
- mkdir -p "$dst"
785
+ cli_env="$(read_current_env cli || true)"
786
+ app_env="$(read_current_env app || true)"
787
+ cli_account="$(read_current_account cli || true)"
788
+ app_account="$(read_current_account app || true)"
789
+ found=0
474
790
 
475
- if command -v rsync >/dev/null 2>&1; then
476
- rsync -a --checksum --delete --exclude 'auth.json' "$src/" "$dst/"
477
- else
478
- find "$dst" -mindepth 1 -maxdepth 1 ! -name 'auth.json' -exec rm -rf -- {} + 2>/dev/null || true
479
- (cd "$src" && tar cf - --exclude ./auth.json .) | (cd "$dst" && tar xpf -)
791
+ while IFS= read -r account; do
792
+ found=1
793
+ marks=""
794
+ [[ "$env" == "$cli_env" && "$account" == "$cli_account" ]] && marks="${marks} [cli-current]"
795
+ [[ "$env" == "$app_env" && "$account" == "$app_account" ]] && marks="${marks} [app-current]"
796
+ echo "- $account$marks"
797
+ done < <(list_accounts_for_env "$env")
798
+
799
+ if [[ "$found" -eq 0 ]]; then
800
+ echo "No accounts in env '$env'. Use: $SCRIPT_NAME account login <account> --env $env"
480
801
  fi
481
- chmod 700 "$dst" 2>/dev/null || true
482
802
  }
483
803
 
484
- sync_default_to_profile() {
485
- local profile="$1"
486
- local src dst
487
- src="${CODEX_SWITCHER_DEFAULT_HOME:-$HOME/.codex}"
488
- dst="$(profile_path "$profile")"
489
- [[ -d "$src" ]] || err "default codex home not found: $src"
490
- ensure_profile "$profile"
491
- sync_overwrite_excluding_auth "$src" "$dst"
492
- log_event INFO "sync_default profile=$profile src=$src"
493
- echo "Synced default data to profile: $profile (auth.json excluded)"
494
- }
495
-
496
- sync_profile_to_profile() {
497
- local from_profile="$1"
498
- local to_profile="$2"
499
- local src dst
804
+ _cmd_account_set_target() {
805
+ local target="$1"
806
+ local env="$2"
807
+ local account="$3"
808
+ set_current_env "$target" "$env"
809
+ set_current_account "$target" "$account"
810
+ install_account_auth_to_env "$env" "$account"
811
+ }
500
812
 
501
- if [[ "$from_profile" == "$to_profile" ]]; then
502
- return 0
813
+ cmd_account_login() {
814
+ local account="$1"
815
+ local env="$2"
816
+ local target="$3"
817
+ local sync_mode="$4"
818
+ local home
819
+
820
+ validate_account_name "$account"
821
+ require_env_exists "$env"
822
+ ensure_env_home "$env"
823
+ home="$(env_home_path "$env")"
824
+
825
+ if [[ "$sync_mode" == "true" && "$env" != "$DEFAULT_ENV_NAME" ]]; then
826
+ sync_overwrite_excluding_auth "$DEFAULT_HOME" "$home"
503
827
  fi
504
828
 
505
- src="$(profile_path "$from_profile")"
506
- dst="$(profile_path "$to_profile")"
507
- require_profile_exists "$from_profile"
508
- ensure_profile "$to_profile"
509
- sync_overwrite_excluding_auth "$src" "$dst"
510
- log_event INFO "sync_profile from=$from_profile to=$to_profile"
511
- echo "Synced profile data: $from_profile -> $to_profile (auth.json excluded)"
829
+ CODEX_HOME="$home" command codex login
830
+ save_env_auth_to_account "$env" "$account" || err "login did not produce auth.json in $home"
831
+
832
+ with_targets "$target" _cmd_account_set_target "$env" "$account"
833
+
834
+ log_event INFO "account_login env=$env account=$account target=$target sync=$sync_mode"
835
+ echo "Logged in account: $env/$account"
512
836
  }
513
837
 
514
- cmd_use() {
515
- local profile="$1"
516
- local sync_mode="${2:-false}"
517
- local from_profile
518
- validate_profile_name "$profile"
838
+ cmd_account_use() {
839
+ local account="$1"
840
+ local env="$2"
841
+ local target="$3"
842
+ local sync_mode="$4"
843
+
844
+ validate_account_name "$account"
845
+ require_env_exists "$env"
846
+ account_has_auth "$env" "$account" || err "account '$account' in env '$env' has no auth.json. run: $SCRIPT_NAME account login $account --env $env"
519
847
 
520
848
  if [[ "$sync_mode" == "true" ]]; then
521
- from_profile="$(read_current cli || true)"
522
- validate_profile_name "$from_profile"
523
- sync_profile_to_profile "$from_profile" "$profile"
849
+ warn "same-env account switch only replaces auth.json; --sync is ignored"
850
+ fi
851
+
852
+ with_targets "$target" _cmd_account_set_target "$env" "$account"
853
+ log_event INFO "account_use env=$env account=$account target=$target"
854
+ echo "Switched $target account to: $env/$account"
855
+ }
856
+
857
+ cmd_account_logout() {
858
+ local account="$1"
859
+ local env="$2"
860
+ local target="$3"
861
+ local cli_env app_env cli_account app_account
862
+
863
+ validate_env_name "$env"
864
+ if [[ -z "$account" ]]; then
865
+ if [[ "$target" == "both" ]]; then
866
+ account="$(read_current_account cli || true)"
867
+ else
868
+ account="$(read_current_account "$target" || true)"
869
+ fi
870
+ fi
871
+ validate_account_name "$account"
872
+ require_env_exists "$env"
873
+
874
+ rm -f -- "$(account_auth_path "$env" "$account")"
875
+
876
+ cli_env="$(read_current_env cli || true)"
877
+ app_env="$(read_current_env app || true)"
878
+ cli_account="$(read_current_account cli || true)"
879
+ app_account="$(read_current_account app || true)"
880
+
881
+ if [[ "$env" == "$cli_env" && "$account" == "$cli_account" ]]; then
882
+ set_current_account cli "$DEFAULT_ACCOUNT_NAME"
883
+ ensure_target_auth_installed cli
884
+ fi
885
+ if [[ "$env" == "$app_env" && "$account" == "$app_account" ]]; then
886
+ set_current_account app "$DEFAULT_ACCOUNT_NAME"
887
+ ensure_target_auth_installed app
524
888
  fi
525
889
 
526
- ensure_profile "$profile"
527
- set_current cli "$profile"
528
- log_event INFO "cli_use profile=$profile sync=$sync_mode"
890
+ if [[ "$account" == "$DEFAULT_ACCOUNT_NAME" ]]; then
891
+ rm -f -- "$(env_home_path "$env")/auth.json"
892
+ fi
893
+
894
+ log_event INFO "account_logout env=$env account=$account target=$target"
895
+ echo "Logged out account: $env/$account"
896
+ }
897
+
898
+ cmd_account_current() {
899
+ local target="${1:-all}"
900
+ case "$target" in
901
+ cli)
902
+ echo "$(read_current_env cli || true)/$(read_current_account cli || true)"
903
+ ;;
904
+ app)
905
+ echo "$(read_current_env app || true)/$(read_current_account app || true)"
906
+ ;;
907
+ all)
908
+ echo "cli: $(read_current_env cli || true)/$(read_current_account cli || true)"
909
+ echo "app: $(read_current_env app || true)/$(read_current_account app || true)"
910
+ ;;
911
+ *)
912
+ err "invalid target '$target' (use cli|app)"
913
+ ;;
914
+ esac
915
+ }
916
+
917
+ cmd_list() {
918
+ local cli_env app_env cli_account app_account
919
+ local env account marks auth_file home metrics
920
+ local email plan usage_5h usage_weekly last_activity source
921
+
922
+ cli_env="$(read_current_env cli || true)"
923
+ app_env="$(read_current_env app || true)"
924
+ cli_account="$(read_current_account cli || true)"
925
+ app_account="$(read_current_account app || true)"
926
+
927
+ printf '%-12s %-30s %-44s %-10s %-16s %-18s %s\n' "ENV" "ACCOUNT" "EMAIL" "PLAN" "5H USAGE" "WEEKLY USAGE" "LAST ACTIVITY"
928
+ while IFS= read -r env; do
929
+ home="$(env_home_path "$env")"
930
+ ensure_env_home "$env"
931
+ local found=0
932
+ while IFS= read -r account; do
933
+ found=1
934
+ marks=""
935
+ [[ "$env" == "$cli_env" && "$account" == "$cli_account" ]] && marks="${marks} [cli-current]"
936
+ [[ "$env" == "$app_env" && "$account" == "$app_account" ]] && marks="${marks} [app-current]"
937
+ auth_file="$(account_auth_path "$env" "$account")"
938
+ [[ -f "$auth_file" ]] || auth_file="$home/auth.json"
939
+
940
+ metrics="$(collect_profile_metrics_tsv "$account" "$auth_file" "$home" || true)"
941
+ if [[ -n "$metrics" ]]; then
942
+ IFS=$'\t' read -r email plan usage_5h usage_weekly last_activity source <<< "$metrics"
943
+ fi
944
+ email="${email:-($account)-}"
945
+ plan="${plan:-unknown}"
946
+ usage_5h="${usage_5h:--}"
947
+ usage_weekly="${usage_weekly:--}"
948
+ last_activity="${last_activity:--}"
949
+ source="${source:-local}"
950
+ [[ "$source" == "api" || "$source" == "local" ]] || source="local"
951
+ last_activity="${last_activity} (${source})"
952
+
953
+ printf '%-12s %-30s %-44s %-10s %-16s %-18s %s\n' "$env" "$account$marks" "$email" "$plan" "$usage_5h" "$usage_weekly" "$last_activity"
954
+ done < <(list_accounts_for_env "$env")
955
+
956
+ if [[ "$found" -eq 0 ]]; then
957
+ metrics="$(collect_profile_metrics_tsv "-" "$home/auth.json" "$home" || true)"
958
+ if [[ -n "$metrics" ]]; then
959
+ IFS=$'\t' read -r email plan usage_5h usage_weekly last_activity source <<< "$metrics"
960
+ fi
961
+ email="${email:--}"
962
+ plan="${plan:-unknown}"
963
+ usage_5h="${usage_5h:--}"
964
+ usage_weekly="${usage_weekly:--}"
965
+ last_activity="${last_activity:--}"
966
+ source="${source:-local}"
967
+ [[ "$source" == "api" || "$source" == "local" ]] || source="local"
968
+ last_activity="${last_activity} (${source})"
969
+ printf '%-12s %-30s %-44s %-10s %-16s %-18s %s\n' "$env" "-" "$email" "$plan" "$usage_5h" "$usage_weekly" "$last_activity"
970
+ fi
971
+ done < <(list_env_names | sort -u)
972
+ }
973
+
974
+ cmd_add() {
975
+ local account="$1"
976
+ local env
977
+ env="$(read_current_env cli || true)"
978
+ cmd_account_add "$account" "$env"
979
+ }
980
+
981
+ cmd_remove() {
982
+ local account="$1"
983
+ local force="$2"
984
+ local env
985
+ env="$(read_current_env cli || true)"
986
+ cmd_account_remove "$account" "$env" "$force"
987
+ }
988
+
989
+ cmd_import_default() {
990
+ local env="$1"
991
+ local with_auth="$2"
992
+ local force="$3"
993
+
994
+ validate_env_name "$env"
995
+ [[ "$env" != "$DEFAULT_ENV_NAME" ]] || err "cannot import into reserved env '$DEFAULT_ENV_NAME'"
996
+
997
+ if env_exists "$env"; then
998
+ if [[ "$force" != "true" ]]; then
999
+ err "env '$env' already exists. use --force to overwrite"
1000
+ fi
1001
+ rm -rf -- "$(env_root_path "$env")" "$ACCOUNTS_DIR/$env"
1002
+ fi
1003
+
1004
+ cmd_env_create "$env" "from-default" ""
1005
+ if [[ "$with_auth" == "true" && -f "$DEFAULT_HOME/auth.json" ]]; then
1006
+ ensure_account_slot "$env" "$DEFAULT_ACCOUNT_NAME"
1007
+ copy_auth_file "$DEFAULT_HOME/auth.json" "$(account_auth_path "$env" "$DEFAULT_ACCOUNT_NAME")"
1008
+ fi
1009
+
1010
+ log_event INFO "import_default_as_env env=$env with_auth=$with_auth force=$force"
1011
+ echo "Imported default data to env: $env"
1012
+ }
1013
+
1014
+ cmd_use_compat() {
1015
+ local account="$1"
1016
+ local sync_mode="${2:-false}"
1017
+ local env
1018
+ env="$(read_current_env cli || true)"
1019
+ cmd_account_use "$account" "$env" "cli" "$sync_mode"
529
1020
  }
530
1021
 
531
- cmd_switch() {
532
- cmd_use "$1" "${2:-false}"
1022
+ cmd_switch_compat() {
1023
+ cmd_use_compat "$1" "${2:-false}"
533
1024
  }
534
1025
 
535
1026
  should_launch_codex() {
@@ -552,17 +1043,17 @@ should_launch_codex() {
552
1043
 
553
1044
  cmd_use_and_maybe_launch() {
554
1045
  local mode_cmd="$1"
555
- local profile="$2"
1046
+ local account="$2"
556
1047
  local sync_mode="${3:-false}"
557
1048
  local launch_mode="${4:-auto}"
558
1049
  shift 4 || true
559
1050
 
560
- with_lock "$mode_cmd" "$profile" "$sync_mode"
1051
+ with_lock "$mode_cmd" "$account" "$sync_mode"
561
1052
 
562
- echo "Switched CLI profile to: $profile"
1053
+ echo "Switched CLI account to: $(read_current_env cli || true)/$(read_current_account cli || true)"
563
1054
  if should_launch_codex "$launch_mode"; then
564
- echo "Launching codex with profile: $profile"
565
- CODEX_HOME="$(profile_path "$profile")" command codex "$@"
1055
+ echo "Launching codex with env/account: $(read_current_env cli || true)/$(read_current_account cli || true)"
1056
+ run_codex_for_target cli "$@"
566
1057
  return
567
1058
  fi
568
1059
 
@@ -570,21 +1061,21 @@ cmd_use_and_maybe_launch() {
570
1061
  echo "Auto launch skipped (non-interactive shell). Use --launch to force start."
571
1062
  fi
572
1063
  echo "Run in current shell if needed:"
573
- echo " export CODEX_HOME='$(profile_path "$profile")'"
1064
+ echo " export CODEX_HOME='$(target_home_path cli)'"
574
1065
  }
575
1066
 
576
1067
  cmd_current() {
577
1068
  local target="${1:-all}"
578
1069
  case "$target" in
579
1070
  cli)
580
- read_current cli || true
1071
+ echo "$(read_current_env cli || true)/$(read_current_account cli || true)"
581
1072
  ;;
582
1073
  app)
583
- read_current app || true
1074
+ echo "$(read_current_env app || true)/$(read_current_account app || true)"
584
1075
  ;;
585
1076
  all)
586
- echo "cli: $(read_current cli || true)"
587
- echo "app: $(read_current app || true)"
1077
+ echo "cli: $(read_current_env cli || true)/$(read_current_account cli || true)"
1078
+ echo "app: $(read_current_env app || true)/$(read_current_account app || true)"
588
1079
  ;;
589
1080
  *)
590
1081
  err "invalid target '$target' (use cli|app)"
@@ -593,31 +1084,31 @@ cmd_current() {
593
1084
  }
594
1085
 
595
1086
  cmd_status() {
596
- local cli_profile app_profile
597
- local cli_ptr_state app_ptr_state
598
- local cli_state app_state
599
- local exit_code
600
-
601
- cli_profile="$(read_current cli)" || cli_ptr_state=$?
602
- app_profile="$(read_current app)" || app_ptr_state=$?
1087
+ local cli_env app_env cli_account app_account
1088
+ local cli_ptr_state app_ptr_state cli_acc_state app_acc_state
1089
+ local cli_state app_state exit_code
1090
+
1091
+ cli_env="$(read_current_env cli)" || cli_ptr_state=$?
1092
+ app_env="$(read_current_env app)" || app_ptr_state=$?
1093
+ cli_account="$(read_current_account cli)" || cli_acc_state=$?
1094
+ app_account="$(read_current_account app)" || app_acc_state=$?
603
1095
  cli_ptr_state="${cli_ptr_state:-0}"
604
1096
  app_ptr_state="${app_ptr_state:-0}"
1097
+ cli_acc_state="${cli_acc_state:-0}"
1098
+ app_acc_state="${app_acc_state:-0}"
605
1099
 
606
- cli_state="$(login_state_for_profile "$cli_profile")"
607
- app_state="$(login_state_for_profile "$app_profile")"
1100
+ cli_state="$(login_state_for_target cli)"
1101
+ app_state="$(login_state_for_target app)"
608
1102
 
609
- echo "cli_current: $cli_profile"
610
- echo "app_current: $app_profile"
611
- echo "cli($cli_profile): $cli_state"
612
- echo "app($app_profile): $app_state"
1103
+ echo "cli_current: $cli_env/$cli_account"
1104
+ echo "app_current: $app_env/$app_account"
1105
+ echo "cli($cli_env/$cli_account): $cli_state"
1106
+ echo "app($app_env/$app_account): $app_state"
613
1107
 
614
1108
  exit_code=0
615
- if [[ "$cli_ptr_state" -eq 2 || "$app_ptr_state" -eq 2 ]]; then
1109
+ if [[ "$cli_ptr_state" -eq 2 || "$app_ptr_state" -eq 2 || "$cli_acc_state" -eq 2 || "$app_acc_state" -eq 2 ]]; then
616
1110
  echo "hint: pointer file corrupted, run: $SCRIPT_NAME recover" >&2
617
1111
  exit_code=2
618
- elif [[ "$cli_state" == "missing-profile" || "$app_state" == "missing-profile" ]]; then
619
- echo "hint: missing profile directory, run: $SCRIPT_NAME recover" >&2
620
- exit_code=2
621
1112
  elif [[ "$cli_state" != "logged-in" || "$app_state" != "logged-in" ]]; then
622
1113
  exit_code=1
623
1114
  fi
@@ -631,85 +1122,76 @@ cmd_exec() {
631
1122
  shift
632
1123
  fi
633
1124
  [[ "$#" -gt 0 ]] || err "missing codex args after --"
634
-
635
- local profile
636
- profile="$(read_current cli || true)"
637
- ensure_profile "$profile"
638
- CODEX_HOME="$(profile_path "$profile")" command codex "$@"
1125
+ run_codex_for_target cli "$@"
639
1126
  }
640
1127
 
641
1128
  cmd_login() {
642
- local profile="${1:-$(read_current cli || true)}"
1129
+ local account="${1:-$(read_current_account cli || true)}"
643
1130
  local sync_mode="${2:-false}"
644
- validate_profile_name "$profile"
645
- ensure_profile "$profile"
646
- if [[ "$sync_mode" == "true" ]]; then
647
- sync_default_to_profile "$profile"
648
- fi
649
- log_event INFO "cli_login profile=$profile sync=$sync_mode"
650
- run_codex_for_profile "$profile" login
1131
+ local env
1132
+ env="$(read_current_env cli || true)"
1133
+ cmd_account_login "$account" "$env" "cli" "$sync_mode"
651
1134
  }
652
1135
 
653
1136
  cmd_logout() {
654
- local profile="${1:-$(read_current cli || true)}"
655
- validate_profile_name "$profile"
656
- ensure_profile "$profile"
657
- log_event INFO "cli_logout profile=$profile"
658
- run_codex_for_profile "$profile" logout
659
- }
660
-
661
- cmd_env() {
662
- local profile="${1:-$(read_current cli || true)}"
663
- validate_profile_name "$profile"
664
- ensure_profile "$profile"
665
- echo "export CODEX_HOME='$(profile_path "$profile")'"
1137
+ local account="${1:-}"
1138
+ local env
1139
+ env="$(read_current_env cli || true)"
1140
+ cmd_account_logout "$account" "$env" "cli"
666
1141
  }
667
1142
 
668
1143
  cmd_app_open() {
669
- local profile="$1"
670
- shift
671
- local app_bin pid login_state
1144
+ local account="${1:-}"
1145
+ shift || true
1146
+ local env app_bin pid login_state use_account
1147
+
1148
+ env="$(read_current_env app || true)"
1149
+ use_account="${account:-$(read_current_account app || true)}"
1150
+ validate_account_name "$use_account"
1151
+ require_env_exists "$env"
1152
+ account_has_auth "$env" "$use_account" || err "account '$use_account' not logged in under env '$env'. run: $SCRIPT_NAME account login $use_account --env $env --target app"
1153
+
1154
+ set_current_env app "$env"
1155
+ set_current_account app "$use_account"
1156
+ ensure_target_auth_installed app
672
1157
 
673
- validate_profile_name "$profile"
674
- require_profile_exists "$profile"
675
- login_state="$(login_state_for_profile "$profile")"
1158
+ login_state="$(login_state_for_target app)"
676
1159
  if [[ "$login_state" != "logged-in" ]]; then
677
- err "profile '$profile' is not logged in. run: $SCRIPT_NAME login $profile"
1160
+ err "account '$use_account' in env '$env' is not logged in"
678
1161
  fi
679
1162
  app_bin="$(resolve_app_bin)"
680
1163
 
681
1164
  app_stop_managed >/dev/null
682
1165
 
683
1166
  mkdir -p "$(dirname "$APP_LOG")"
684
- nohup env CODEX_HOME="$(profile_path "$profile")" CODEX_SWITCHER_MANAGED=1 CODEX_SWITCHER_PROFILE="$profile" "$app_bin" "$@" >>"$APP_LOG" 2>&1 &
1167
+ nohup env CODEX_HOME="$(target_home_path app)" CODEX_SWITCHER_MANAGED=1 CODEX_SWITCHER_ENV="$env" CODEX_SWITCHER_ACCOUNT="$use_account" "$app_bin" "$@" >>"$APP_LOG" 2>&1 &
685
1168
  pid="$!"
686
1169
  printf '%s\n' "$pid" > "$APP_PID_FILE"
687
1170
  chmod 600 "$APP_PID_FILE" 2>/dev/null || true
688
1171
 
689
- set_current app "$profile"
690
- log_event INFO "app_open profile=$profile pid=$pid args=$*"
691
- echo "Opened Codex App with profile: $profile"
1172
+ log_event INFO "app_open env=$env account=$use_account pid=$pid args=$*"
1173
+ echo "Opened Codex App with: $env/$use_account"
692
1174
  echo "App log: $APP_LOG"
693
1175
  }
694
1176
 
695
1177
  cmd_app_use() {
696
- local profile="$1"
697
- shift
698
- cmd_app_open "$profile" "$@"
1178
+ local account="$1"
1179
+ shift || true
1180
+ cmd_app_open "$account" "$@"
699
1181
  }
700
1182
 
701
1183
  cmd_app_logout() {
702
- local profile="${1:-$(read_current app || true)}"
703
- validate_profile_name "$profile"
704
- ensure_profile "$profile"
705
- log_event INFO "app_logout profile=$profile"
706
- run_codex_for_profile "$profile" logout
1184
+ local account="${1:-}"
1185
+ local env
1186
+ env="$(read_current_env app || true)"
1187
+ cmd_account_logout "$account" "$env" "app"
707
1188
  }
708
1189
 
709
1190
  cmd_app_status() {
710
- local profile
711
- profile="$(read_current app || true)"
712
- echo "app_current: $profile"
1191
+ local env account
1192
+ env="$(read_current_env app || true)"
1193
+ account="$(read_current_account app || true)"
1194
+ echo "app_current: $env/$account"
713
1195
  if app_is_running; then
714
1196
  echo "app_process: running(pid=$(cat "$APP_PID_FILE"))"
715
1197
  return 0
@@ -813,44 +1295,34 @@ cmd_upgrade() {
813
1295
 
814
1296
  cmd_recover() {
815
1297
  local dry_run="${1:-false}"
816
- local cli_profile app_profile cli_ptr_state app_ptr_state first
817
-
818
- cli_profile="$(read_current cli)" || cli_ptr_state=$?
819
- app_profile="$(read_current app)" || app_ptr_state=$?
820
- cli_ptr_state="${cli_ptr_state:-0}"
821
- app_ptr_state="${app_ptr_state:-0}"
822
-
823
- if [[ ! -d "$(profile_path "$cli_profile")" ]]; then
824
- if first="$(first_profile_name)"; then
825
- cli_profile="$first"
826
- else
827
- cli_profile="$(default_profile_for cli)"
828
- ensure_profile "$cli_profile"
1298
+ local target env account
1299
+
1300
+ for target in cli app; do
1301
+ env="$(read_current_env "$target" || true)"
1302
+ account="$(read_current_account "$target" || true)"
1303
+ validate_env_name_noexit "$env" || env="$DEFAULT_ENV_NAME"
1304
+ if [[ "$env" != "$DEFAULT_ENV_NAME" ]] && [[ ! -d "$(env_home_path "$env")" ]]; then
1305
+ env="$DEFAULT_ENV_NAME"
1306
+ fi
1307
+ validate_name_noexit "$account" || account="$DEFAULT_ACCOUNT_NAME"
1308
+ if ! account_has_auth "$env" "$account"; then
1309
+ account="$DEFAULT_ACCOUNT_NAME"
829
1310
  fi
830
- cli_ptr_state=3
831
- fi
832
1311
 
833
- if [[ ! -d "$(profile_path "$app_profile")" ]]; then
834
- if [[ -d "$(profile_path "$cli_profile")" ]]; then
835
- app_profile="$cli_profile"
836
- elif first="$(first_profile_name)"; then
837
- app_profile="$first"
838
- else
839
- app_profile="$(default_profile_for app)"
840
- ensure_profile "$app_profile"
1312
+ if [[ "$dry_run" != "true" ]]; then
1313
+ set_current_env "$target" "$env"
1314
+ set_current_account "$target" "$account"
1315
+ ensure_target_auth_installed "$target"
841
1316
  fi
842
- app_ptr_state=3
843
- fi
1317
+
1318
+ echo "recover($target): $env/$account"
1319
+ done
844
1320
 
845
1321
  if [[ "$dry_run" == "true" ]]; then
846
- echo "recover(dry-run): cli=$cli_profile app=$app_profile"
847
1322
  return 0
848
1323
  fi
849
1324
 
850
- set_current cli "$cli_profile"
851
- set_current app "$app_profile"
852
- log_event INFO "recover cli=$cli_profile app=$app_profile"
853
- echo "Recovered pointers: cli=$cli_profile app=$app_profile"
1325
+ log_event INFO "recover cli=$(read_current_env cli || true)/$(read_current_account cli || true) app=$(read_current_env app || true)/$(read_current_account app || true)"
854
1326
  }
855
1327
 
856
1328
  scan_logs_for_secrets() {
@@ -869,17 +1341,27 @@ scan_logs_for_secrets() {
869
1341
  cmd_check() {
870
1342
  command -v codex >/dev/null 2>&1 || err "codex command not found in PATH"
871
1343
  resolve_app_bin >/dev/null
872
-
873
1344
  ensure_dirs
874
- local pstate
875
- read_current cli >/dev/null || pstate=$?
876
- if [[ "${pstate:-0}" -eq 2 ]]; then
877
- err "current_cli pointer corrupted; run: $SCRIPT_NAME recover"
1345
+
1346
+ local pstate=0
1347
+ read_current_env cli >/dev/null || pstate=$?
1348
+ if [[ "$pstate" -eq 2 ]]; then
1349
+ err "current_cli_env pointer corrupted; run: $SCRIPT_NAME recover"
1350
+ fi
1351
+ pstate=0
1352
+ read_current_env app >/dev/null || pstate=$?
1353
+ if [[ "$pstate" -eq 2 ]]; then
1354
+ err "current_app_env pointer corrupted; run: $SCRIPT_NAME recover"
1355
+ fi
1356
+ pstate=0
1357
+ read_current_account cli >/dev/null || pstate=$?
1358
+ if [[ "$pstate" -eq 2 ]]; then
1359
+ err "current_cli_account pointer corrupted; run: $SCRIPT_NAME recover"
878
1360
  fi
879
1361
  pstate=0
880
- read_current app >/dev/null || pstate=$?
1362
+ read_current_account app >/dev/null || pstate=$?
881
1363
  if [[ "$pstate" -eq 2 ]]; then
882
- err "current_app pointer corrupted; run: $SCRIPT_NAME recover"
1364
+ err "current_app_account pointer corrupted; run: $SCRIPT_NAME recover"
883
1365
  fi
884
1366
 
885
1367
  if ! scan_logs_for_secrets; then
@@ -893,16 +1375,19 @@ cmd_check() {
893
1375
  cmd_doctor() {
894
1376
  local fix="${1:-false}"
895
1377
  local issues=0
896
- local state_perm profiles_perm
1378
+ local state_perm envs_perm accounts_perm
897
1379
 
898
1380
  ensure_dirs
899
1381
  state_perm="$(perm_of "$STATE_DIR")"
900
- profiles_perm="$(perm_of "$PROFILES_DIR")"
1382
+ envs_perm="$(perm_of "$ENVS_DIR")"
1383
+ accounts_perm="$(perm_of "$ACCOUNTS_DIR")"
901
1384
 
902
1385
  echo "state_dir: $STATE_DIR (perm=$state_perm)"
903
- echo "profiles_dir: $PROFILES_DIR (perm=$profiles_perm)"
904
- echo "cli_current: $(read_current cli || true)"
905
- echo "app_current: $(read_current app || true)"
1386
+ echo "envs_dir: $ENVS_DIR (perm=$envs_perm)"
1387
+ echo "accounts_dir: $ACCOUNTS_DIR (perm=$accounts_perm)"
1388
+ echo "default_home: $DEFAULT_HOME (perm=$(perm_of "$DEFAULT_HOME"))"
1389
+ echo "cli_current: $(read_current_env cli || true)/$(read_current_account cli || true)"
1390
+ echo "app_current: $(read_current_env app || true)/$(read_current_account app || true)"
906
1391
 
907
1392
  if ! command -v codex >/dev/null 2>&1; then
908
1393
  echo "- codex in PATH: missing"
@@ -918,20 +1403,9 @@ cmd_doctor() {
918
1403
  issues=1
919
1404
  fi
920
1405
 
921
- if [[ "$state_perm" != "700" ]]; then
922
- echo "- state dir permission not 700"
923
- issues=1
924
- if [[ "$fix" == "true" ]]; then
925
- chmod 700 "$STATE_DIR" || true
926
- fi
927
- fi
928
-
929
- if [[ "$profiles_perm" != "700" ]]; then
930
- echo "- profiles dir permission not 700"
1406
+ if [[ "$state_perm" != "700" || "$envs_perm" != "700" || "$accounts_perm" != "700" ]]; then
931
1407
  issues=1
932
- if [[ "$fix" == "true" ]]; then
933
- chmod 700 "$PROFILES_DIR" || true
934
- fi
1408
+ echo "- directory permissions are not strict 700"
935
1409
  fi
936
1410
 
937
1411
  if ! scan_logs_for_secrets; then
@@ -940,23 +1414,9 @@ cmd_doctor() {
940
1414
  echo "- redaction check: ok"
941
1415
  fi
942
1416
 
943
- local pstate=0
944
- read_current cli >/dev/null || pstate=$?
945
- if [[ "$pstate" -eq 2 ]]; then
946
- echo "- current_cli pointer corrupted"
947
- issues=1
948
- fi
949
- pstate=0
950
- read_current app >/dev/null || pstate=$?
951
- if [[ "$pstate" -eq 2 ]]; then
952
- echo "- current_app pointer corrupted"
953
- issues=1
954
- fi
955
-
956
1417
  if [[ "$fix" == "true" ]]; then
1418
+ chmod 700 "$STATE_DIR" "$ENVS_DIR" "$ACCOUNTS_DIR" "$DEFAULT_HOME" 2>/dev/null || true
957
1419
  with_lock cmd_recover false
958
- chmod 700 "$STATE_DIR" "$PROFILES_DIR" 2>/dev/null || true
959
- find "$PROFILES_DIR" -mindepth 1 -maxdepth 1 -type d -exec chmod 700 {} \; 2>/dev/null || true
960
1420
  issues=0
961
1421
  if ! scan_logs_for_secrets; then
962
1422
  issues=1
@@ -987,12 +1447,265 @@ main() {
987
1447
  shift || true
988
1448
 
989
1449
  case "$cmd" in
1450
+ env)
1451
+ local sub="${1:-}"
1452
+ if [[ -z "$sub" ]]; then
1453
+ usage
1454
+ exit 1
1455
+ fi
1456
+ shift || true
1457
+ case "$sub" in
1458
+ list)
1459
+ [[ "$#" -eq 0 ]] || err "usage: $SCRIPT_NAME env list"
1460
+ cmd_env_list
1461
+ ;;
1462
+ create)
1463
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME env create <env> [--empty|--from-default|--from-env <src>]"
1464
+ local env="$1"
1465
+ shift
1466
+ local mode="from-default"
1467
+ local src_env=""
1468
+ while [[ "$#" -gt 0 ]]; do
1469
+ case "$1" in
1470
+ --empty)
1471
+ mode="empty"
1472
+ ;;
1473
+ --from-default)
1474
+ mode="from-default"
1475
+ ;;
1476
+ --from-env)
1477
+ shift
1478
+ [[ "$#" -gt 0 ]] || err "missing source env after --from-env"
1479
+ mode="from-env"
1480
+ src_env="$1"
1481
+ ;;
1482
+ *)
1483
+ err "unknown option: $1"
1484
+ ;;
1485
+ esac
1486
+ shift
1487
+ done
1488
+ with_lock cmd_env_create "$env" "$mode" "$src_env"
1489
+ ;;
1490
+ use)
1491
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME env use <env> [--target cli|app|both]"
1492
+ local env="$1"
1493
+ shift
1494
+ local target="cli"
1495
+ while [[ "$#" -gt 0 ]]; do
1496
+ case "$1" in
1497
+ --target)
1498
+ shift
1499
+ [[ "$#" -gt 0 ]] || err "missing value after --target"
1500
+ target="$1"
1501
+ ;;
1502
+ *)
1503
+ err "unknown option: $1"
1504
+ ;;
1505
+ esac
1506
+ shift
1507
+ done
1508
+ with_lock cmd_env_use "$env" "$target"
1509
+ ;;
1510
+ remove)
1511
+ [[ "$#" -ge 1 && "$#" -le 2 ]] || err "usage: $SCRIPT_NAME env remove <env> [--force]"
1512
+ local env="$1"
1513
+ local force="false"
1514
+ if [[ "${2:-}" == "--force" ]]; then
1515
+ force="true"
1516
+ elif [[ -n "${2:-}" ]]; then
1517
+ err "unknown option: ${2:-}"
1518
+ fi
1519
+ with_lock cmd_env_remove "$env" "$force"
1520
+ ;;
1521
+ current)
1522
+ [[ "$#" -le 1 ]] || err "usage: $SCRIPT_NAME env current [cli|app]"
1523
+ cmd_env_current "${1:-all}"
1524
+ ;;
1525
+ path)
1526
+ [[ "$#" -le 1 ]] || err "usage: $SCRIPT_NAME env path [env]"
1527
+ cmd_env_path "${1:-}"
1528
+ ;;
1529
+ *)
1530
+ err "unknown env subcommand: $sub"
1531
+ ;;
1532
+ esac
1533
+ ;;
1534
+ account)
1535
+ local sub="${1:-}"
1536
+ [[ -n "$sub" ]] || err "usage: $SCRIPT_NAME account <list|add|remove|login|use|logout|current> ..."
1537
+ shift || true
1538
+ case "$sub" in
1539
+ list)
1540
+ local env="$(read_current_env cli || true)"
1541
+ while [[ "$#" -gt 0 ]]; do
1542
+ case "$1" in
1543
+ --env)
1544
+ shift
1545
+ [[ "$#" -gt 0 ]] || err "missing env after --env"
1546
+ env="$1"
1547
+ ;;
1548
+ *)
1549
+ err "unknown option: $1"
1550
+ ;;
1551
+ esac
1552
+ shift
1553
+ done
1554
+ cmd_account_list "$env"
1555
+ ;;
1556
+ add)
1557
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME account add <account> [--env <env>]"
1558
+ local account="$1"
1559
+ shift
1560
+ local env="$(read_current_env cli || true)"
1561
+ while [[ "$#" -gt 0 ]]; do
1562
+ case "$1" in
1563
+ --env)
1564
+ shift
1565
+ [[ "$#" -gt 0 ]] || err "missing env after --env"
1566
+ env="$1"
1567
+ ;;
1568
+ *)
1569
+ err "unknown option: $1"
1570
+ ;;
1571
+ esac
1572
+ shift
1573
+ done
1574
+ with_lock cmd_account_add "$account" "$env"
1575
+ ;;
1576
+ remove)
1577
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME account remove <account> [--env <env>] [--force]"
1578
+ local account="$1"
1579
+ shift
1580
+ local env="$(read_current_env cli || true)"
1581
+ local force="false"
1582
+ while [[ "$#" -gt 0 ]]; do
1583
+ case "$1" in
1584
+ --env)
1585
+ shift
1586
+ [[ "$#" -gt 0 ]] || err "missing env after --env"
1587
+ env="$1"
1588
+ ;;
1589
+ --force)
1590
+ force="true"
1591
+ ;;
1592
+ *)
1593
+ err "unknown option: $1"
1594
+ ;;
1595
+ esac
1596
+ shift
1597
+ done
1598
+ with_lock cmd_account_remove "$account" "$env" "$force"
1599
+ ;;
1600
+ login)
1601
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME account login <account> [--env <env>] [--target cli|app|both] [--sync|--no-sync]"
1602
+ local account="$1"
1603
+ shift
1604
+ local env="$(read_current_env cli || true)"
1605
+ local target="cli"
1606
+ local sync_mode="false"
1607
+ while [[ "$#" -gt 0 ]]; do
1608
+ case "$1" in
1609
+ --env)
1610
+ shift
1611
+ [[ "$#" -gt 0 ]] || err "missing env after --env"
1612
+ env="$1"
1613
+ ;;
1614
+ --target)
1615
+ shift
1616
+ [[ "$#" -gt 0 ]] || err "missing target after --target"
1617
+ target="$1"
1618
+ ;;
1619
+ --sync)
1620
+ sync_mode="true"
1621
+ ;;
1622
+ --no-sync)
1623
+ sync_mode="false"
1624
+ ;;
1625
+ *)
1626
+ err "unknown option: $1"
1627
+ ;;
1628
+ esac
1629
+ shift
1630
+ done
1631
+ with_lock cmd_account_login "$account" "$env" "$target" "$sync_mode"
1632
+ ;;
1633
+ use)
1634
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME account use <account> [--env <env>] [--target cli|app|both] [--sync|--no-sync]"
1635
+ local account="$1"
1636
+ shift
1637
+ local env="$(read_current_env cli || true)"
1638
+ local target="cli"
1639
+ local sync_mode="false"
1640
+ while [[ "$#" -gt 0 ]]; do
1641
+ case "$1" in
1642
+ --env)
1643
+ shift
1644
+ [[ "$#" -gt 0 ]] || err "missing env after --env"
1645
+ env="$1"
1646
+ ;;
1647
+ --target)
1648
+ shift
1649
+ [[ "$#" -gt 0 ]] || err "missing target after --target"
1650
+ target="$1"
1651
+ ;;
1652
+ --sync)
1653
+ sync_mode="true"
1654
+ ;;
1655
+ --no-sync)
1656
+ sync_mode="false"
1657
+ ;;
1658
+ *)
1659
+ err "unknown option: $1"
1660
+ ;;
1661
+ esac
1662
+ shift
1663
+ done
1664
+ with_lock cmd_account_use "$account" "$env" "$target" "$sync_mode"
1665
+ ;;
1666
+ logout)
1667
+ local account=""
1668
+ local env="$(read_current_env cli || true)"
1669
+ local target="cli"
1670
+ if [[ "$#" -gt 0 && "${1:-}" != --* ]]; then
1671
+ account="$1"
1672
+ shift
1673
+ fi
1674
+ while [[ "$#" -gt 0 ]]; do
1675
+ case "$1" in
1676
+ --env)
1677
+ shift
1678
+ [[ "$#" -gt 0 ]] || err "missing env after --env"
1679
+ env="$1"
1680
+ ;;
1681
+ --target)
1682
+ shift
1683
+ [[ "$#" -gt 0 ]] || err "missing target after --target"
1684
+ target="$1"
1685
+ ;;
1686
+ *)
1687
+ err "unknown option: $1"
1688
+ ;;
1689
+ esac
1690
+ shift
1691
+ done
1692
+ with_lock cmd_account_logout "$account" "$env" "$target"
1693
+ ;;
1694
+ current)
1695
+ [[ "$#" -le 1 ]] || err "usage: $SCRIPT_NAME account current [cli|app]"
1696
+ cmd_account_current "${1:-all}"
1697
+ ;;
1698
+ *)
1699
+ err "unknown account subcommand: $sub"
1700
+ ;;
1701
+ esac
1702
+ ;;
990
1703
  add)
991
- [[ "$#" -eq 1 ]] || err "usage: $SCRIPT_NAME add <profile>"
1704
+ [[ "$#" -eq 1 ]] || err "usage: $SCRIPT_NAME add <account>"
992
1705
  with_lock cmd_add "$1"
993
1706
  ;;
994
1707
  remove)
995
- [[ "$#" -ge 1 && "$#" -le 2 ]] || err "usage: $SCRIPT_NAME remove <profile> [--force]"
1708
+ [[ "$#" -ge 1 && "$#" -le 2 ]] || err "usage: $SCRIPT_NAME remove <account> [--force]"
996
1709
  local force="false"
997
1710
  if [[ "${2:-}" == "--force" ]]; then
998
1711
  force="true"
@@ -1006,8 +1719,8 @@ main() {
1006
1719
  cmd_list
1007
1720
  ;;
1008
1721
  import-default)
1009
- [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME import-default <profile> [--with-auth] [--force]"
1010
- local profile="$1"
1722
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME import-default <env> [--with-auth] [--force]"
1723
+ local env="$1"
1011
1724
  local with_auth="false"
1012
1725
  local force="false"
1013
1726
  shift
@@ -1025,11 +1738,11 @@ main() {
1025
1738
  esac
1026
1739
  shift
1027
1740
  done
1028
- with_lock cmd_import_default "$profile" "$with_auth" "$force"
1741
+ with_lock cmd_import_default "$env" "$with_auth" "$force"
1029
1742
  ;;
1030
1743
  use)
1031
- [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME use <profile> [--sync|--no-sync] [--launch|--no-launch] [-- <codex args...>]"
1032
- local profile="$1"
1744
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME use <account> [--sync|--no-sync] [--launch|--no-launch] [-- <codex args...>]"
1745
+ local account="$1"
1033
1746
  shift
1034
1747
  local sync="false"
1035
1748
  local launch="auto"
@@ -1064,14 +1777,14 @@ main() {
1064
1777
  launch="true"
1065
1778
  fi
1066
1779
  if [[ "${#codex_args[@]}" -gt 0 ]]; then
1067
- cmd_use_and_maybe_launch cmd_use "$profile" "$sync" "$launch" "${codex_args[@]}"
1780
+ cmd_use_and_maybe_launch cmd_use_compat "$account" "$sync" "$launch" "${codex_args[@]}"
1068
1781
  else
1069
- cmd_use_and_maybe_launch cmd_use "$profile" "$sync" "$launch"
1782
+ cmd_use_and_maybe_launch cmd_use_compat "$account" "$sync" "$launch"
1070
1783
  fi
1071
1784
  ;;
1072
1785
  switch)
1073
- [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME switch <profile> [--sync|--no-sync] [--launch|--no-launch] [-- <codex args...>]"
1074
- local profile="$1"
1786
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME switch <account> [--sync|--no-sync] [--launch|--no-launch] [-- <codex args...>]"
1787
+ local account="$1"
1075
1788
  shift
1076
1789
  local sync="false"
1077
1790
  local launch="auto"
@@ -1106,9 +1819,9 @@ main() {
1106
1819
  launch="true"
1107
1820
  fi
1108
1821
  if [[ "${#codex_args[@]}" -gt 0 ]]; then
1109
- cmd_use_and_maybe_launch cmd_switch "$profile" "$sync" "$launch" "${codex_args[@]}"
1822
+ cmd_use_and_maybe_launch cmd_switch_compat "$account" "$sync" "$launch" "${codex_args[@]}"
1110
1823
  else
1111
- cmd_use_and_maybe_launch cmd_switch "$profile" "$sync" "$launch"
1824
+ cmd_use_and_maybe_launch cmd_switch_compat "$account" "$sync" "$launch"
1112
1825
  fi
1113
1826
  ;;
1114
1827
  current)
@@ -1123,8 +1836,8 @@ main() {
1123
1836
  cmd_exec "$@"
1124
1837
  ;;
1125
1838
  login)
1126
- [[ "$#" -le 2 ]] || err "usage: $SCRIPT_NAME login [profile] [--sync|--no-sync]"
1127
- local profile=""
1839
+ [[ "$#" -le 2 ]] || err "usage: $SCRIPT_NAME login [account] [--sync|--no-sync]"
1840
+ local account=""
1128
1841
  local sync="false"
1129
1842
  local arg
1130
1843
  for arg in "$@"; do
@@ -1136,44 +1849,43 @@ main() {
1136
1849
  sync="false"
1137
1850
  ;;
1138
1851
  *)
1139
- if [[ -z "$profile" ]]; then
1140
- profile="$arg"
1852
+ if [[ -z "$account" ]]; then
1853
+ account="$arg"
1141
1854
  else
1142
1855
  err "unknown option: $arg"
1143
1856
  fi
1144
1857
  ;;
1145
1858
  esac
1146
1859
  done
1147
- with_lock cmd_login "${profile:-}" "$sync"
1860
+ with_lock cmd_login "${account:-}" "$sync"
1148
1861
  ;;
1149
1862
  logout)
1150
- [[ "$#" -le 1 ]] || err "usage: $SCRIPT_NAME logout [profile]"
1863
+ [[ "$#" -le 1 ]] || err "usage: $SCRIPT_NAME logout [account]"
1151
1864
  with_lock cmd_logout "${1:-}"
1152
1865
  ;;
1153
- env)
1154
- [[ "$#" -le 1 ]] || err "usage: $SCRIPT_NAME env [profile]"
1155
- cmd_env "${1:-}"
1156
- ;;
1157
1866
  app)
1158
1867
  local sub="${1:-}"
1159
1868
  [[ -n "$sub" ]] || err "usage: $SCRIPT_NAME app <open|use|logout|status|stop|current> ..."
1160
1869
  shift || true
1161
1870
  case "$sub" in
1162
1871
  open)
1163
- local profile="${1:-$(read_current app || true)}"
1164
- if [[ "$#" -gt 0 ]]; then shift; fi
1872
+ local account=""
1873
+ if [[ "$#" -gt 0 && "${1:-}" != "--" ]]; then
1874
+ account="$1"
1875
+ shift
1876
+ fi
1165
1877
  if [[ "${1:-}" == "--" ]]; then shift; fi
1166
- with_lock cmd_app_open "$profile" "$@"
1878
+ with_lock cmd_app_open "$account" "$@"
1167
1879
  ;;
1168
1880
  use)
1169
- [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME app use <profile> [-- <app args...>]"
1170
- local profile="$1"
1881
+ [[ "$#" -ge 1 ]] || err "usage: $SCRIPT_NAME app use <account> [-- <app args...>]"
1882
+ local account="$1"
1171
1883
  shift
1172
1884
  if [[ "${1:-}" == "--" ]]; then shift; fi
1173
- with_lock cmd_app_use "$profile" "$@"
1885
+ with_lock cmd_app_use "$account" "$@"
1174
1886
  ;;
1175
1887
  logout)
1176
- [[ "$#" -le 1 ]] || err "usage: $SCRIPT_NAME app logout [profile]"
1888
+ [[ "$#" -le 1 ]] || err "usage: $SCRIPT_NAME app logout [account]"
1177
1889
  with_lock cmd_app_logout "${1:-}"
1178
1890
  ;;
1179
1891
  status)
@@ -1186,7 +1898,7 @@ main() {
1186
1898
  ;;
1187
1899
  current)
1188
1900
  [[ "$#" -eq 0 ]] || err "usage: $SCRIPT_NAME app current"
1189
- cmd_current app
1901
+ echo "$(read_current_env app || true)/$(read_current_account app || true)"
1190
1902
  ;;
1191
1903
  *)
1192
1904
  err "unknown app subcommand: $sub"