From c66ed28b451a7021c73e080f5a1d7f02fd7cee88 Mon Sep 17 00:00:00 2001 From: Rishi Khare Date: Wed, 18 Feb 2026 17:41:41 -0500 Subject: [PATCH 1/3] feat: add claude-pick interactive model picker - Add `claude-pick` bash script: reads PROVIDER_TYPE from .env, fetches available models (NVIDIA NIM, OpenRouter, LM Studio), and launches Claude with the selected model via fzf. Falls back to direct launch when MODEL_PICKER=false. - Add MODEL_PICKER=false flag to .env.example. - Document setup in README (fzf install, alias, fixed-model alias pattern). Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 8 ++- README.md | 32 +++++++++ claude-pick | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 1 deletion(-) create mode 100755 claude-pick diff --git a/.env.example b/.env.example index b3b93941..2e4d9c96 100644 --- a/.env.example +++ b/.env.example @@ -61,4 +61,10 @@ FAST_PREFIX_DETECTION=true ENABLE_NETWORK_PROBE_MOCK=true ENABLE_TITLE_GENERATION_SKIP=true ENABLE_SUGGESTION_MODE_SKIP=true -ENABLE_FILEPATH_EXTRACTION_MOCK=true \ No newline at end of file +ENABLE_FILEPATH_EXTRACTION_MOCK=true + + +# Model Picker: set to true to show an interactive fzf model selector. +# Run `claude-pick` to launch Claude. With MODEL_PICKER=false it launches +# directly using the MODEL configured above. +MODEL_PICKER=false \ No newline at end of file diff --git a/README.md b/README.md index 6fa4254f..25c0c232 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,38 @@ class MyPlatform(MessagingPlatform): --- +## Model Picker + +`claude-pick` is an interactive model selector that lets you choose a model from your active provider before launching Claude. + +**1. Install [fzf](https://github.com/junegunn/fzf)** (highly recommended for the interactive picker): + +```bash +brew install fzf # macOS/Linux +``` + +**2. Enable the picker in `.env`:** + +```env +MODEL_PICKER=true +``` + +**3. Add the alias to `~/.zshrc` or `~/.bashrc`:** + +```bash +alias claude-pick="/path/to/free-claude-code/claude-pick" +``` + +Then reload your shell (`source ~/.zshrc`) and run `claude-pick` to pick a model and launch Claude. With `MODEL_PICKER=false` (the default), `claude-pick` skips the picker and launches Claude with the model set in `MODEL`. + +**Skip the picker with a fixed model** (no alias or picker needed): + +```bash +alias claude-kimi='ANTHROPIC_BASE_URL="http://localhost:8082" ANTHROPIC_AUTH_TOKEN="freecc:moonshotai/kimi-k2.5" claude' +``` + +--- + ## Contributing Contributions are welcome! Here are some ways to help: diff --git a/claude-pick b/claude-pick new file mode 100755 index 00000000..37bd8c81 --- /dev/null +++ b/claude-pick @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# claude-pick — Interactive model picker for free-claude-code +# Usage: claude-pick [extra claude args...] +# +# Set MODEL_PICKER=true in .env (or export it) to enable the interactive picker. +# With MODEL_PICKER=false (default) this script launches Claude against the local +# proxy with the default model configured in .env. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MODELS_FILE="$SCRIPT_DIR/nvidia_nim_models.json" +ENV_FILE="${CLAUDE_PICK_ENV_FILE:-$SCRIPT_DIR/.env}" +PORT="${CLAUDE_PICK_PORT:-8082}" +BASE_URL="http://localhost:$PORT" +OPENROUTER_MODELS_URL="https://openrouter.ai/api/v1/models" +DEFAULT_LM_STUDIO_BASE_URL="http://localhost:1234/v1" + +if ! command -v python3 >/dev/null 2>&1; then + echo "Error: python3 is required." >&2 + exit 1 +fi + +read_env_value() { + local key="$1" + [[ -f "$ENV_FILE" ]] || return 0 + + local raw + raw="$(grep -E "^[[:space:]]*${key}[[:space:]]*=" "$ENV_FILE" | tail -n 1 || true)" + raw="${raw#*=}" + raw="${raw%%#*}" + raw="$(echo "$raw" | xargs || true)" + raw="${raw%\"}" + raw="${raw#\"}" + raw="${raw%\'}" + raw="${raw#\'}" + echo "$raw" +} + +# ── MODEL_PICKER check ──────────────────────────────────────────────────────── +# Env var takes precedence over .env file value. +_mp_raw="${MODEL_PICKER:-$(read_env_value MODEL_PICKER)}" +_mp_raw="$(printf '%s' "${_mp_raw:-false}" | tr '[:upper:]' '[:lower:]')" + +if [[ ! "$_mp_raw" =~ ^(1|true|yes|on)$ ]]; then + ANTHROPIC_AUTH_TOKEN="freecc" ANTHROPIC_BASE_URL="$BASE_URL" exec claude "$@" +fi + +# ── Picker enabled — fzf required ──────────────────────────────────────────── +if ! command -v fzf >/dev/null 2>&1; then + echo "Error: fzf is required for the model picker." >&2 + echo "Install it from: https://github.com/junegunn/fzf" >&2 + exit 1 +fi + +parse_models_from_json() { + python3 -c ' +import json, sys +try: + payload = json.load(sys.stdin) +except Exception: + sys.exit(0) +for item in payload.get("data", []): + model_id = item.get("id") + if model_id: + print(model_id) +' +} + +get_nvidia_models() { + if [[ ! -f "$MODELS_FILE" ]]; then + echo "Error: $MODELS_FILE not found." >&2 + echo "Run: curl \"https://integrate.api.nvidia.com/v1/models\" > nvidia_nim_models.json" >&2 + exit 1 + fi + + python3 -c ' +import json, sys +with open(sys.argv[1], "r", encoding="utf-8") as f: + payload = json.load(f) +for item in payload.get("data", []): + model_id = item.get("id") + if model_id: + print(model_id) +' "$MODELS_FILE" +} + +get_openrouter_models() { + if ! command -v curl >/dev/null 2>&1; then + echo "Error: curl is required for OpenRouter model discovery." >&2 + exit 1 + fi + + local openrouter_key + openrouter_key="${OPENROUTER_API_KEY:-$(read_env_value OPENROUTER_API_KEY)}" + + local response + if [[ -n "$openrouter_key" ]]; then + if ! response="$(curl -fsSL -H "Authorization: Bearer $openrouter_key" "$OPENROUTER_MODELS_URL")"; then + echo "Error: Failed to fetch OpenRouter models." >&2 + exit 1 + fi + else + if ! response="$(curl -fsSL "$OPENROUTER_MODELS_URL")"; then + echo "Error: Failed to fetch OpenRouter models." >&2 + exit 1 + fi + fi + + parse_models_from_json <<< "$response" +} + +get_lmstudio_models() { + if ! command -v curl >/dev/null 2>&1; then + echo "Error: curl is required for LM Studio model discovery." >&2 + exit 1 + fi + + local lm_base + lm_base="${LM_STUDIO_BASE_URL:-$(read_env_value LM_STUDIO_BASE_URL)}" + lm_base="${lm_base:-$DEFAULT_LM_STUDIO_BASE_URL}" + + local models_url + if [[ "$lm_base" == */v1 ]]; then + models_url="${lm_base}/models" + else + models_url="${lm_base}/v1/models" + fi + + local response + if ! response="$(curl -fsSL "$models_url")"; then + echo "Error: Failed to query LM Studio models at $models_url" >&2 + echo "Start LM Studio server first (Developer tab or: lms server start)." >&2 + exit 1 + fi + + parse_models_from_json <<< "$response" +} + +provider="${CLAUDE_PICK_PROVIDER:-$(read_env_value PROVIDER_TYPE)}" +provider="${provider:-nvidia_nim}" + +prompt="Select a model> " +case "$provider" in + nvidia_nim) + models="$(get_nvidia_models)" + prompt="Select a NVIDIA NIM model> " + ;; + open_router|openrouter) + models="$(get_openrouter_models)" + prompt="Select an OpenRouter model> " + ;; + lmstudio|lm_studio|lm-studio) + models="$(get_lmstudio_models)" + prompt="Select an LM Studio model> " + ;; + *) + echo "Error: Unsupported PROVIDER_TYPE='$provider'." >&2 + echo "Expected one of: nvidia_nim, open_router, lmstudio" >&2 + exit 1 + ;; +esac + +models="$(printf "%s\n" "$models" | sed '/^[[:space:]]*$/d' | sort -u)" +if [[ -z "$models" ]]; then + echo "Error: No models found for provider '$provider'." >&2 + exit 1 +fi + +model="$(printf "%s\n" "$models" | fzf --prompt="$prompt" --height=40% --reverse)" + +if [[ -z "${model:-}" ]]; then + echo "No model selected." >&2 + exit 1 +fi + +echo "Launching Claude with provider: $provider, model: $model" >&2 +ANTHROPIC_AUTH_TOKEN="freecc:$model" ANTHROPIC_BASE_URL="$BASE_URL" exec claude "$@" From 142dd587c83b2d25ffd1d18c9f5119ab2a848941 Mon Sep 17 00:00:00 2001 From: Rishi Khare Date: Wed, 18 Feb 2026 18:01:50 -0500 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20remove=20MODEL=5FPICKER=20flag?= =?UTF-8?q?=20=E2=80=94=20claude-pick=20always=20picks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flag was unnecessary: running claude-pick implies wanting the picker. Remove MODEL_PICKER from claude-pick and README, restore .env.example to upstream. Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 8 +------- README.md | 10 ++-------- claude-pick | 14 -------------- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index 2e4d9c96..b3b93941 100644 --- a/.env.example +++ b/.env.example @@ -61,10 +61,4 @@ FAST_PREFIX_DETECTION=true ENABLE_NETWORK_PROBE_MOCK=true ENABLE_TITLE_GENERATION_SKIP=true ENABLE_SUGGESTION_MODE_SKIP=true -ENABLE_FILEPATH_EXTRACTION_MOCK=true - - -# Model Picker: set to true to show an interactive fzf model selector. -# Run `claude-pick` to launch Claude. With MODEL_PICKER=false it launches -# directly using the MODEL configured above. -MODEL_PICKER=false \ No newline at end of file +ENABLE_FILEPATH_EXTRACTION_MOCK=true \ No newline at end of file diff --git a/README.md b/README.md index 25c0c232..a962341e 100644 --- a/README.md +++ b/README.md @@ -445,19 +445,13 @@ class MyPlatform(MessagingPlatform): brew install fzf # macOS/Linux ``` -**2. Enable the picker in `.env`:** - -```env -MODEL_PICKER=true -``` - -**3. Add the alias to `~/.zshrc` or `~/.bashrc`:** +**2. Add the alias to `~/.zshrc` or `~/.bashrc`:** ```bash alias claude-pick="/path/to/free-claude-code/claude-pick" ``` -Then reload your shell (`source ~/.zshrc`) and run `claude-pick` to pick a model and launch Claude. With `MODEL_PICKER=false` (the default), `claude-pick` skips the picker and launches Claude with the model set in `MODEL`. +Then reload your shell (`source ~/.zshrc`) and run `claude-pick` to pick a model and launch Claude. **Skip the picker with a fixed model** (no alias or picker needed): diff --git a/claude-pick b/claude-pick index 37bd8c81..33d3b45a 100755 --- a/claude-pick +++ b/claude-pick @@ -1,10 +1,6 @@ #!/usr/bin/env bash # claude-pick — Interactive model picker for free-claude-code # Usage: claude-pick [extra claude args...] -# -# Set MODEL_PICKER=true in .env (or export it) to enable the interactive picker. -# With MODEL_PICKER=false (default) this script launches Claude against the local -# proxy with the default model configured in .env. set -euo pipefail @@ -37,16 +33,6 @@ read_env_value() { echo "$raw" } -# ── MODEL_PICKER check ──────────────────────────────────────────────────────── -# Env var takes precedence over .env file value. -_mp_raw="${MODEL_PICKER:-$(read_env_value MODEL_PICKER)}" -_mp_raw="$(printf '%s' "${_mp_raw:-false}" | tr '[:upper:]' '[:lower:]')" - -if [[ ! "$_mp_raw" =~ ^(1|true|yes|on)$ ]]; then - ANTHROPIC_AUTH_TOKEN="freecc" ANTHROPIC_BASE_URL="$BASE_URL" exec claude "$@" -fi - -# ── Picker enabled — fzf required ──────────────────────────────────────────── if ! command -v fzf >/dev/null 2>&1; then echo "Error: fzf is required for the model picker." >&2 echo "Install it from: https://github.com/junegunn/fzf" >&2 From 406de89ae3f8a4968fae9b4cea0ea866ec5a8839 Mon Sep 17 00:00:00 2001 From: Rishi Khare Date: Wed, 18 Feb 2026 18:09:26 -0500 Subject: [PATCH 3/3] docs: clarify absolute path required for claude-pick alias Co-Authored-By: Claude Sonnet 4.6 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a962341e..c545ac40 100644 --- a/README.md +++ b/README.md @@ -448,7 +448,8 @@ brew install fzf # macOS/Linux **2. Add the alias to `~/.zshrc` or `~/.bashrc`:** ```bash -alias claude-pick="/path/to/free-claude-code/claude-pick" +# Use the absolute path to your cloned repo +alias claude-pick="/absolute/path/to/free-claude-code/claude-pick" ``` Then reload your shell (`source ~/.zshrc`) and run `claude-pick` to pick a model and launch Claude.