Skip to content

Instantly share code, notes, and snippets.

@yeoweizheng
Last active March 30, 2026 17:23
Show Gist options
  • Select an option

  • Save yeoweizheng/49c48fc7ff7e8c0f5165ca290e2162d4 to your computer and use it in GitHub Desktop.

Select an option

Save yeoweizheng/49c48fc7ff7e8c0f5165ca290e2162d4 to your computer and use it in GitHub Desktop.
OpenCode Profile Switcher
#!/bin/sh
# Switch between different opencode auth profiles under ~/.local/share/opencode/auth.json
# Profiles are saved as auth-<profile_name>.json
# Current profile saved in current_profile.txt
die() {
printf '%s\n' "$1" >&2
exit 1
}
info() {
printf '%s\n' "$1"
}
get_paths() {
BASE_DIR=${OPENCODE_PROFILE_BASE_DIR:-"$HOME/.local/share/opencode"}
AUTH_FILE="$BASE_DIR/auth.json"
CURRENT_PROFILE_FILE="$BASE_DIR/current_profile.txt"
PROFILES=
PROFILE_COUNT=0
CURRENT_RECORDED_PROFILE=
VALID_CURRENT_PROFILE=
CURRENT_STATUS=
}
ensure_base_dir() {
if [ ! -d "$BASE_DIR" ]; then
mkdir -p "$BASE_DIR" || die "Failed to create base directory: $BASE_DIR"
fi
}
load_profiles() {
PROFILES=
PROFILE_COUNT=0
for path in "$BASE_DIR"/auth-*.json; do
[ -f "$path" ] || continue
name=$(basename "$path")
name=${name#auth-}
name=${name%.json}
if [ -z "$PROFILES" ]; then
PROFILES=$name
else
PROFILES=$(printf '%s\n%s\n' "$PROFILES" "$name")
fi
done
if [ -n "$PROFILES" ]; then
PROFILES=$(printf '%s\n' "$PROFILES" | sort)
set -- $PROFILES
PROFILE_COUNT=$#
fi
}
get_current_status() {
CURRENT_RECORDED_PROFILE=
VALID_CURRENT_PROFILE=
if [ -f "$CURRENT_PROFILE_FILE" ]; then
IFS= read -r CURRENT_RECORDED_PROFILE < "$CURRENT_PROFILE_FILE" || CURRENT_RECORDED_PROFILE=
if [ -f "$BASE_DIR/auth-$CURRENT_RECORDED_PROFILE.json" ]; then
VALID_CURRENT_PROFILE=$CURRENT_RECORDED_PROFILE
CURRENT_STATUS=$CURRENT_RECORDED_PROFILE
else
CURRENT_STATUS="$CURRENT_RECORDED_PROFILE (missing profile file)"
fi
elif [ -f "$AUTH_FILE" ]; then
CURRENT_STATUS='unknown profile'
else
CURRENT_STATUS='unset'
fi
}
print_header() {
printf '=== OpenCode Profile Utility ===\n'
printf 'Current profile: %s\n\n' "$CURRENT_STATUS"
}
prompt_main_action() {
while :; do
printf '1) Switch to a profile\n'
printf '2) Save current auth.json to a profile\n'
printf 'q) Quit\n'
printf 'Choose an action [1-2, q]: '
IFS= read -r action || action=q
case $action in
1|2|q|quit)
MAIN_ACTION=$action
return 0
;;
*)
info 'Invalid input. Please enter 1, 2, or q.'
printf '\n'
;;
esac
done
}
print_profile_list() {
if [ "$PROFILE_COUNT" -eq 0 ]; then
return 0
fi
index=1
set -- $PROFILES
for profile_name do
printf '%s) %s\n' "$index" "$profile_name"
index=$((index + 1))
done
}
prompt_profile_selection() {
if [ "$PROFILE_COUNT" -eq 0 ]; then
info 'No saved profiles found.'
return 1
fi
print_profile_list
while :; do
printf 'Select a profile number [1-%s, q]: ' "$PROFILE_COUNT"
IFS= read -r selection || return 1
case $selection in
q|quit)
return 1
;;
''|*[!0-9]*)
info 'Invalid input. Please enter a profile number or q.'
;;
*)
if [ "$selection" -lt 1 ] || [ "$selection" -gt "$PROFILE_COUNT" ]; then
info "Invalid input. Please enter a number from 1 to $PROFILE_COUNT."
else
set -- $PROFILES
index=1
for profile_name do
if [ "$index" -eq "$selection" ]; then
switch_to_profile "$profile_name"
fi
index=$((index + 1))
done
fi
;;
esac
done
}
validate_profile_name() {
profile_name=$1
case $profile_name in
'')
info 'Profile name cannot be empty.'
return 1
;;
*[!A-Za-z0-9_-]*)
info 'Profile name may contain only letters, numbers, underscores, and hyphens.'
return 1
;;
*)
return 0
;;
esac
}
confirm_overwrite() {
profile_name=$1
printf "Profile '%s' already exists. Overwrite? [y/N]: " "$profile_name"
IFS= read -r answer || return 1
case $answer in
y|yes)
return 0
;;
q|quit)
return 1
;;
*)
return 1
;;
esac
}
prompt_save_profile_name() {
while :; do
if [ -n "$VALID_CURRENT_PROFILE" ]; then
printf 'Profile name [%s]: ' "$VALID_CURRENT_PROFILE"
else
printf 'Profile name: '
fi
IFS= read -r profile_name || return 1
case $profile_name in
q|quit)
return 1
;;
esac
if [ -z "$profile_name" ] && [ -n "$VALID_CURRENT_PROFILE" ]; then
profile_name=$VALID_CURRENT_PROFILE
fi
if ! validate_profile_name "$profile_name"; then
continue
fi
target_file="$BASE_DIR/auth-$profile_name.json"
if [ -f "$target_file" ] && ! confirm_overwrite "$profile_name"; then
continue
fi
save_current_to_profile "$profile_name"
done
}
write_current_profile() {
printf '%s\n' "$1" > "$CURRENT_PROFILE_FILE" || die 'Failed to update current_profile.txt'
}
switch_to_profile() {
profile_name=$1
source_file="$BASE_DIR/auth-$profile_name.json"
[ -f "$source_file" ] || die "Profile file not found: $source_file"
cp "$source_file" "$AUTH_FILE" || die 'Failed to update auth.json'
write_current_profile "$profile_name"
info "Switched to profile '$profile_name'."
exit 0
}
save_current_to_profile() {
profile_name=$1
target_file="$BASE_DIR/auth-$profile_name.json"
[ -f "$AUTH_FILE" ] || die 'No auth.json found. Cannot save a profile.'
cp "$AUTH_FILE" "$target_file" || die "Failed to save profile '$profile_name'"
write_current_profile "$profile_name"
info "Saved current auth to profile '$profile_name'."
exit 0
}
main() {
get_paths
ensure_base_dir
while :; do
load_profiles
get_current_status
print_header
prompt_main_action
case $MAIN_ACTION in
1)
if prompt_profile_selection; then
:
fi
;;
2)
if [ ! -f "$AUTH_FILE" ]; then
info 'No auth.json found. Nothing to save.'
else
prompt_save_profile_name || :
fi
;;
q|quit)
exit 0
;;
esac
printf '\n'
done
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment