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