Last active
March 10, 2026 21:27
-
-
Save tuannvm/6fe6cf37c05265a9ee0acbd3d2da52d4 to your computer and use it in GitHub Desktop.
ccodex unified setup/run script for Claude Code + CLIProxyAPI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -u | |
| CCODEX_CMD="${HOME}/.local/bin/ccodex" | |
| ALIAS_FILE="${HOME}/.oh-my-zsh/custom/ccodex-alias.zsh" | |
| CCODEX_INSTALL_URL="${CCODEX_INSTALL_URL:-https://gist.github.com/tuannvm/6fe6cf37c05265a9ee0acbd3d2da52d4/raw/ccodex}" | |
| has_cmd() { | |
| command -v "$1" >/dev/null 2>&1 | |
| } | |
| cliproxy_bin_cmd() { | |
| if has_cmd cliproxyapi; then | |
| echo "cliproxyapi" | |
| elif has_cmd cliproxy; then | |
| echo "cliproxy" | |
| fi | |
| } | |
| cliproxy_cmd() { | |
| cliproxy_bin_cmd | |
| } | |
| cliproxy_running() { | |
| local code | |
| code="$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8317/v1/models 2>/dev/null)" | |
| [[ "$code" == "200" || "$code" == "401" ]] | |
| } | |
| cliproxy_auth_configured() { | |
| local cmd out models auth_dir configured_auth_dir config_file | |
| # Fast path 1: persisted Codex auth token files in default location. | |
| auth_dir="${HOME}/.cli-proxy-api" | |
| # Fast path 2: config-defined auth-dir (supports custom locations). | |
| for config_file in \ | |
| "${CLIPROXYAPI_CONFIG:-}" \ | |
| "${HOME}/.cli-proxy-api/config.yaml" \ | |
| "/home/linuxbrew/.linuxbrew/etc/cliproxyapi.conf" \ | |
| "/opt/homebrew/etc/cliproxyapi.conf" \ | |
| "/etc/cliproxyapi.conf"; do | |
| [[ -n "$config_file" && -f "$config_file" ]] || continue | |
| configured_auth_dir="$(sed -n 's/^[[:space:]]*auth-dir:[[:space:]]*\([^#]*\).*$/\1/p' "$config_file" | head -1)" | |
| [[ -n "$configured_auth_dir" ]] || continue | |
| configured_auth_dir="${configured_auth_dir#${configured_auth_dir%%[![:space:]]*}}" | |
| configured_auth_dir="${configured_auth_dir%${configured_auth_dir##*[![:space:]]}}" | |
| configured_auth_dir="${configured_auth_dir#\'}" | |
| configured_auth_dir="${configured_auth_dir%\'}" | |
| configured_auth_dir="${configured_auth_dir#\"}" | |
| configured_auth_dir="${configured_auth_dir%\"}" | |
| configured_auth_dir="${configured_auth_dir/#\~/$HOME}" | |
| configured_auth_dir="${configured_auth_dir/\$HOME/$HOME}" | |
| configured_auth_dir="${configured_auth_dir/\$\{HOME\}/$HOME}" | |
| if [[ -n "$configured_auth_dir" ]]; then | |
| auth_dir="$configured_auth_dir" | |
| break | |
| fi | |
| done | |
| compgen -G "${auth_dir%/}/codex-*.json" >/dev/null 2>&1 && return 0 | |
| cmd="$(cliproxy_cmd)" | |
| if [[ -n "$cmd" ]]; then | |
| # Status can report auth entries/files before model list refresh. | |
| out="$($cmd status 2>&1 || true)" | |
| echo "$out" | grep -Eq '([1-9][0-9]* auth entries|[1-9][0-9]* auth files)' && return 0 | |
| fi | |
| # API path: authenticated models listing. | |
| models="$(curl -s -H "Authorization: Bearer sk-dummy" http://127.0.0.1:8317/v1/models 2>/dev/null || true)" | |
| [[ -n "$models" ]] || return 1 | |
| if has_cmd python3; then | |
| printf "%s" "$models" | python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); sys.exit(0 if d.get("object")=="list" and isinstance(d.get("data"), list) and len(d.get("data", []))>0 else 1)' >/dev/null 2>&1 | |
| return $? | |
| fi | |
| echo "$models" | grep -Eq '"object"[[:space:]]*:[[:space:]]*"list"' || return 1 | |
| echo "$models" | grep -Eq '"data"[[:space:]]*:[[:space:]]*\[' | |
| } | |
| status_line() { | |
| local label="$1" | |
| local ok="$2" | |
| if [[ "$ok" == "1" ]]; then | |
| printf " [OK] %s\n" "$label" | |
| else | |
| printf " [MISSING] %s\n" "$label" | |
| fi | |
| } | |
| print_status() { | |
| local s_cmd=0 s_proxy=0 s_auth=0 s_alias=0 s_shell=0 s_claude=0 s_vscode=0 | |
| [[ -n "$(cliproxy_cmd)" ]] && s_cmd=1 | |
| cliproxy_running && s_proxy=1 | |
| cliproxy_auth_configured && s_auth=1 | |
| has_alias_file && s_alias=1 | |
| shell_integration_configured && s_shell=1 | |
| has_cmd claude && s_claude=1 | |
| has_cmd code && s_vscode=1 | |
| echo "" | |
| echo "ccodex status" | |
| status_line "CLIProxyAPI command available" "$s_cmd" | |
| status_line "CLIProxyAPI running on 127.0.0.1:8317" "$s_proxy" | |
| status_line "ChatGPT/Codex auth configured" "$s_auth" | |
| status_line "ccodex/co aliases installed" "$s_alias" | |
| status_line "Shell rc integration configured" "$s_shell" | |
| status_line "Claude CLI available" "$s_claude" | |
| status_line "VS Code CLI available (optional)" "$s_vscode" | |
| } | |
| has_alias_file() { | |
| [[ -f "$ALIAS_FILE" ]] || return 1 | |
| grep -Eq '^[[:space:]]*ccodex\(\) \{' "$ALIAS_FILE" 2>/dev/null || return 1 | |
| grep -Eq '^[[:space:]]*co\(\) \{' "$ALIAS_FILE" 2>/dev/null | |
| } | |
| shell_integration_configured() { | |
| grep -Fq "$ALIAS_FILE" "${HOME}/.zshrc" 2>/dev/null && return 0 | |
| grep -Fq "$ALIAS_FILE" "${HOME}/.bashrc" 2>/dev/null | |
| } | |
| persist_self_to_local_bin() { | |
| local cmd_path | |
| cmd_path="$(command -v ccodex 2>/dev/null || true)" | |
| if [[ "$cmd_path" == "$CCODEX_CMD" && -x "$CCODEX_CMD" ]]; then | |
| return 0 | |
| fi | |
| mkdir -p "$(dirname "$CCODEX_CMD")" | |
| if [[ -r "$0" && "$0" != "bash" && "$0" != "sh" ]]; then | |
| cp "$0" "$CCODEX_CMD" | |
| else | |
| if ! has_cmd curl; then | |
| echo "Error: curl is required to install ccodex to $CCODEX_CMD" | |
| return 1 | |
| fi | |
| curl -fsSL "$CCODEX_INSTALL_URL" -o "$CCODEX_CMD" || return 1 | |
| fi | |
| chmod +x "$CCODEX_CMD" || return 1 | |
| echo "Installed command: $CCODEX_CMD" | |
| } | |
| install_cliproxyapi() { | |
| local cmd | |
| cmd="$(cliproxy_cmd)" | |
| if [[ -n "$cmd" ]]; then | |
| echo "CLIProxyAPI already available: $cmd" | |
| return 0 | |
| fi | |
| if ! has_cmd brew; then | |
| echo "Error: Homebrew not found. Install Homebrew first to install CLIProxyAPI." | |
| return 1 | |
| fi | |
| echo "Installing CLIProxyAPI via Homebrew: brew install cliproxyapi" | |
| if brew install cliproxyapi >/dev/null 2>&1; then | |
| cmd="$(cliproxy_cmd)" | |
| if [[ -n "$cmd" ]]; then | |
| echo "Installed via Homebrew: $cmd" | |
| return 0 | |
| fi | |
| fi | |
| echo "Homebrew install did not produce a usable command." | |
| echo "Install manually and rerun: ccodex --install" | |
| return 1 | |
| } | |
| login_chatgpt() { | |
| local cmd | |
| cmd="$(cliproxy_cmd)" | |
| if [[ -z "$cmd" ]]; then | |
| echo "CLIProxyAPI command not found. Run: ccodex --install" | |
| return 1 | |
| fi | |
| echo "Launching ChatGPT/Codex OAuth login in browser..." | |
| echo "Command: ${cmd} -codex-login" | |
| "$cmd" -codex-login | |
| } | |
| start_cliproxy_if_needed() { | |
| local cmd log_file i | |
| if cliproxy_running; then | |
| return 0 | |
| fi | |
| cmd="$(cliproxy_cmd)" | |
| if [[ -z "$cmd" ]]; then | |
| install_cliproxyapi || return 1 | |
| cmd="$(cliproxy_cmd)" | |
| fi | |
| if [[ -z "$cmd" ]]; then | |
| echo "Error: CLIProxyAPI command unavailable after install attempts." | |
| return 1 | |
| fi | |
| echo "Starting CLIProxyAPI in background..." | |
| log_file="${HOME}/.cache/ccodex-cliproxy.log" | |
| mkdir -p "$(dirname "$log_file")" | |
| nohup "$cmd" > "$log_file" 2>&1 & | |
| for i in 1 2 3 4 5 6 7 8 9 10; do | |
| if cliproxy_running; then | |
| echo "CLIProxyAPI is running." | |
| return 0 | |
| fi | |
| sleep 1 | |
| done | |
| echo "Error: CLIProxyAPI did not become ready." | |
| echo "Check logs: $log_file" | |
| return 1 | |
| } | |
| install_aliases() { | |
| mkdir -p "$(dirname "$ALIAS_FILE")" | |
| cat > "$ALIAS_FILE" <<EOF | |
| ccodex() { | |
| command "${CCODEX_CMD}" "\$@" | |
| } | |
| if ! typeset -f co >/dev/null 2>&1; then | |
| co() { | |
| command "${CCODEX_CMD}" --run "\$@" | |
| } | |
| fi | |
| claude-openai() { | |
| command "${CCODEX_CMD}" --run "\$@" | |
| } | |
| claude-openai-ready() { | |
| command "${CCODEX_CMD}" --ready | |
| } | |
| EOF | |
| echo "Installed alias file: $ALIAS_FILE" | |
| } | |
| ensure_source_line_in_rc() { | |
| local rc_file="$1" | |
| local source_line="[ -f \"${ALIAS_FILE}\" ] && source \"${ALIAS_FILE}\"" | |
| touch "$rc_file" | |
| if grep -Fq "$ALIAS_FILE" "$rc_file" 2>/dev/null; then | |
| echo "Shell integration already present: $rc_file" | |
| return 0 | |
| fi | |
| { | |
| echo "" | |
| echo "# ccodex aliases" | |
| echo "$source_line" | |
| } >> "$rc_file" | |
| echo "Added shell integration to: $rc_file" | |
| } | |
| configure_shell_integration() { | |
| local choice interactive | |
| interactive=1 | |
| [[ -t 0 ]] || interactive=0 | |
| if [[ "$interactive" -eq 0 ]]; then | |
| if [[ -f "${HOME}/.zshrc" ]]; then | |
| ensure_source_line_in_rc "${HOME}/.zshrc" | |
| elif [[ -f "${HOME}/.bashrc" ]]; then | |
| ensure_source_line_in_rc "${HOME}/.bashrc" | |
| else | |
| ensure_source_line_in_rc "${HOME}/.zshrc" | |
| fi | |
| return 0 | |
| fi | |
| echo "" | |
| echo "Choose shell rc file for ccodex aliases:" | |
| echo " 1) ~/.zshrc" | |
| echo " 2) ~/.bashrc" | |
| echo " 3) Both" | |
| echo " 4) Skip (manual source)" | |
| read -r -p "Select [1-4] (default: 1): " choice | |
| choice="${choice:-1}" | |
| case "$choice" in | |
| 1) | |
| ensure_source_line_in_rc "${HOME}/.zshrc" | |
| ;; | |
| 2) | |
| ensure_source_line_in_rc "${HOME}/.bashrc" | |
| ;; | |
| 3) | |
| ensure_source_line_in_rc "${HOME}/.zshrc" | |
| ensure_source_line_in_rc "${HOME}/.bashrc" | |
| ;; | |
| 4) | |
| echo "Skipped rc integration." | |
| ;; | |
| *) | |
| echo "Invalid choice. Using ~/.zshrc" | |
| ensure_source_line_in_rc "${HOME}/.zshrc" | |
| ;; | |
| esac | |
| } | |
| ensure_claude_cli() { | |
| if has_cmd claude; then | |
| return 0 | |
| fi | |
| echo "Error: claude CLI not found in PATH" | |
| echo "Install Claude Code CLI first, then rerun ccodex." | |
| return 1 | |
| } | |
| wait_for_auth_after_login() { | |
| local i | |
| for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30; do | |
| if cliproxy_auth_configured; then | |
| return 0 | |
| fi | |
| sleep 1 | |
| done | |
| return 1 | |
| } | |
| run_ccodex() { | |
| ensure_claude_cli || return 1 | |
| start_cliproxy_if_needed || return 1 | |
| if ! cliproxy_auth_configured; then | |
| echo "ChatGPT/Codex auth not configured. Starting login..." | |
| login_chatgpt || return 1 | |
| if ! wait_for_auth_after_login; then | |
| echo "Error: ChatGPT/Codex auth still not configured after login." | |
| return 1 | |
| fi | |
| fi | |
| mkdir -p "${TMPDIR:-/tmp/claude-${UID}}" | |
| exec env -u ANTHROPIC_API_KEY \ | |
| -u CLAUDE_API_KEY \ | |
| -u ANTHROPIC_AUTH_TOKEN \ | |
| -u ANTHROPIC_BASE_URL \ | |
| -u ANTHROPIC_MODEL \ | |
| -u ANTHROPIC_DEFAULT_OPUS_MODEL \ | |
| -u ANTHROPIC_DEFAULT_SONNET_MODEL \ | |
| -u ANTHROPIC_DEFAULT_HAIKU_MODEL \ | |
| -u CLAUDE_CODE_SUBAGENT_MODEL \ | |
| ANTHROPIC_AUTH_TOKEN="sk-dummy" \ | |
| ANTHROPIC_BASE_URL="http://127.0.0.1:8317" \ | |
| API_TIMEOUT_MS=120000 \ | |
| ANTHROPIC_DEFAULT_OPUS_MODEL="gpt-5.3-codex(xhigh)" \ | |
| ANTHROPIC_MODEL="gpt-5.3-codex(medium)" \ | |
| ANTHROPIC_DEFAULT_SONNET_MODEL="gpt-5.3-codex(high)" \ | |
| ANTHROPIC_DEFAULT_HAIKU_MODEL="gpt-5.3-codex(low)" \ | |
| CLAUDE_CODE_SUBAGENT_MODEL="gpt-5.3-codex(medium)" \ | |
| TMPDIR="${TMPDIR:-/tmp/claude-${UID}}" \ | |
| CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ | |
| DISABLE_COST_WARNINGS=1 \ | |
| DISABLE_TELEMETRY=1 \ | |
| DISABLE_ERROR_REPORTING=1 \ | |
| CLAUDE_CONFIG_DIR="${HOME}/.claude-openai" \ | |
| claude "$@" | |
| } | |
| ready_check() { | |
| print_status | |
| if [[ -n "$(cliproxy_cmd)" ]] && cliproxy_running && cliproxy_auth_configured && has_alias_file && shell_integration_configured && has_cmd claude; then | |
| echo "" | |
| echo "Ready: run 'ccodex' or 'co'." | |
| return 0 | |
| fi | |
| echo "" | |
| echo "Not ready: run 'ccodex --install' then 'ccodex --login'." | |
| return 1 | |
| } | |
| install_all() { | |
| persist_self_to_local_bin || return 1 | |
| install_cliproxyapi || return 1 | |
| install_aliases || return 1 | |
| configure_shell_integration || return 1 | |
| start_cliproxy_if_needed || return 1 | |
| if ! cliproxy_auth_configured; then | |
| echo "ChatGPT/Codex auth not found. Starting login..." | |
| login_chatgpt || return 1 | |
| if ! wait_for_auth_after_login; then | |
| echo "Error: ChatGPT/Codex auth still not configured after login." | |
| return 1 | |
| fi | |
| fi | |
| ready_check || true | |
| print_next_steps | |
| return 0 | |
| } | |
| print_next_steps() { | |
| local cmd | |
| cmd="$(cliproxy_cmd)" | |
| echo "" | |
| echo "Next steps" | |
| if [[ -n "$cmd" ]]; then | |
| echo " 1) Run GPT via Claude Code: ccodex" | |
| echo " (auto-starts $cmd and auto-runs login if missing)" | |
| else | |
| echo " 1) Run: ccodex" | |
| echo " (it will auto-install/start CLIProxyAPI and prompt login if needed)" | |
| fi | |
| echo " 2) Or shorthand: co" | |
| echo " 3) Reload shell if aliases not loaded: source ~/.zshrc" | |
| echo "" | |
| echo "One-liner (when hosted):" | |
| echo " curl -fsSL <your-script-url> | bash -s" | |
| echo " curl -fsSL <your-script-url> | bash -s -- --install" | |
| } | |
| running_as_piped_bootstrap() { | |
| local base | |
| base="${0##*/}" | |
| [[ ! -t 0 ]] && [[ "$base" == "bash" || "$base" == "sh" ]] | |
| } | |
| show_help() { | |
| cat <<'EOF' | |
| Usage: ccodex [mode] [-- claude_args...] | |
| Modes: | |
| --install Install dependencies + aliases (idempotent, auto-login if needed) | |
| --login Run ChatGPT/Codex OAuth login | |
| --status Show setup status | |
| --ready Show status and strict readiness result | |
| --run [args...] Run Claude Code routed through CLIProxyAPI GPT models | |
| Default behavior: | |
| - no args: piped bootstrap (curl|bash -s) runs install; installed command runs Claude directly | |
| EOF | |
| } | |
| main() { | |
| case "${1:-}" in | |
| --install) | |
| install_all || return 1 | |
| ;; | |
| --login) | |
| login_chatgpt | |
| ;; | |
| --status) | |
| print_status | |
| ;; | |
| --ready) | |
| ready_check | |
| ;; | |
| --run) | |
| shift | |
| run_ccodex "$@" | |
| ;; | |
| --help|-h) | |
| show_help | |
| ;; | |
| "") | |
| if running_as_piped_bootstrap; then | |
| install_all || return 1 | |
| else | |
| run_ccodex | |
| fi | |
| ;; | |
| *) | |
| run_ccodex "$@" | |
| ;; | |
| esac | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment