#!/usr/bin/env bash # sfclaude-desktop — configure Claude Desktop to use SiliconFlow as inference backend. # # Edits ~/Library/Application Support/Claude-3p/configLibrary/{_meta,}.json # and restarts Claude Desktop. macOS only. # # Usage: # sfclaude-desktop # configure to DeepSeek V4 PRO and restart # sfclaude-desktop kimi # moonshotai/Kimi-K2-Instruct-0905 # sfclaude-desktop glm # Pro/zai-org/GLM-5 # sfclaude-desktop minimax # Pro/MiniMaxAI/MiniMax-M2.5 # sfclaude-desktop qwen # Qwen/Qwen2.5-Coder # sfclaude-desktop yi # 01-ai/Yi-1.5 # sfclaude-desktop r1 # deepseek-ai/DeepSeek-R1 # sfclaude-desktop update # git pull # sfclaude-desktop -h # help # # Reads SF_API_KEY from env or ~/.zshrc & friends (else prompts). # Base URL: https://api.siliconflow.cn/ set -euo pipefail CONFIG_DIR="$HOME/Library/Application Support/Claude-3p/configLibrary" META="$CONFIG_DIR/_meta.json" ENTRY_NAME="sfclaude-desktop" AUTH_SCHEME="bearer" MAIN_MODEL="deepseek-ai/DeepSeek-V4-PRO" FAST_MODEL="deepseek-ai/DeepSeek-V3" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; update) SELF_DIR="$(cd "$(dirname "$0")" && pwd)"; echo "sfclaude-desktop: pulling latest from $SELF_DIR ..."; git -C "$SELF_DIR" pull && echo "updated." || { echo "git pull failed." >&2; exit 1; }; exit 0 ;; max|pro) MAIN_MODEL="deepseek-ai/DeepSeek-V4-PRO"; FAST_MODEL="deepseek-ai/DeepSeek-V3"; shift ;; fast|flash|lite) MAIN_MODEL="deepseek-ai/DeepSeek-V3"; FAST_MODEL="deepseek-ai/DeepSeek-V3"; shift ;; kimi) MAIN_MODEL="moonshotai/Kimi-K2-Instruct-0905"; FAST_MODEL="deepseek-ai/DeepSeek-V3"; shift ;; glm) MAIN_MODEL="Pro/zai-org/GLM-5"; FAST_MODEL="Pro/zai-org/GLM-5"; shift ;; minimax) MAIN_MODEL="Pro/MiniMaxAI/MiniMax-M2.5"; FAST_MODEL="Pro/MiniMaxAI/MiniMax-M2.5"; shift ;; qwen) MAIN_MODEL="Qwen/Qwen2.5-Coder"; FAST_MODEL="Qwen/Qwen2.5-Coder"; shift ;; yi) MAIN_MODEL="01-ai/Yi-1.5"; FAST_MODEL="01-ai/Yi-1.5"; shift ;; r1) MAIN_MODEL="deepseek-ai/DeepSeek-R1"; FAST_MODEL="deepseek-ai/DeepSeek-R1"; shift ;; *) echo "sfclaude-desktop: unknown argument: $1" >&2; exit 2 ;; esac done preflight() { [[ "$(uname)" == "Darwin" ]] || { echo "macOS only." >&2; exit 1; } [[ -d "/Applications/Claude.app" ]] || { echo "Claude.app not found." >&2; exit 1; } [[ -x "/usr/bin/jq" ]] || { echo "jq not found." >&2; exit 1; } local ds="$HOME/Library/Application Support/Claude/developer_settings.json" if [[ ! -f "$ds" ]] || ! /usr/bin/jq -e '.allowDevTools==true' "$ds" >/dev/null 2>&1; then echo "sfclaude-desktop: Enable Developer Mode first (Help → Troubleshooting → Enable Developer Mode)" >&2; exit 1 fi } resolve_api_key() { if [[ -n "${SF_API_KEY:-}" ]]; then printf '%s' "$SF_API_KEY"; return 0; fi local rc found="" for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do [[ -r "$rc" ]] || continue found="$(grep -v '^[[:space:]]*#' "$rc" 2>/dev/null|grep -E '^[[:space:]]*export[[:space:]]+SF_API_KEY='|tail -1|sed -E 's/^[^=]*=//; s/^"(.*)"$/\1/; s/^'\''(.*)'\''$/\1/'||true)" if [[ -n "$found" ]]; then printf '%s' "$found"; return 0; fi done local key key="$(osascript 2>/dev/null <<'APPLESCRIPT'||true try;set theKey to text returned of (display dialog "SF API Key not found.\nPaste your SiliconFlow API Key:" default answer "" with hidden answer with title "sfclaude-desktop");return theKey;end try APPLESCRIPT )" if [[ -z "$key" ]]; then echo "sfclaude-desktop: no API Key provided. Aborting." >&2; exit 1; fi printf '%s' "$key" } confirm_or_abort() { echo; echo "About to: $1"; echo "Press Enter to continue, Ctrl-C to abort."; read -r _; } write_entry() { local uuid="$1"; local entry_path="$CONFIG_DIR/${uuid}.json"; local tmp="${entry_path}.tmp" local content content="$(jq -n --arg baseUrl "$BASE_URL" --arg apiKey "$SF_KEY" --arg auth "$AUTH_SCHEME" --arg main "$MAIN_MODEL" --arg fast "$FAST_MODEL" \ '{inferenceProvider:"gateway",inferenceGatewayBaseUrl:$baseUrl,inferenceGatewayApiKey:$apiKey,inferenceGatewayAuthScheme:$auth,unstableDisableModelVerification:true,inferenceModels:([{name:$main,supports1m:false}]+(if $fast!=$main then[{name:$fast,supports1m:false}]else[]end))}')" printf '%s' "$content" > "$tmp"; chmod 600 "$tmp"; mv "$tmp" "$entry_path" } ensure_meta_entry() { mkdir -p "$CONFIG_DIR" local existing_uuid="" if [[ -f "$META" ]]; then existing_uuid="$(jq -r --arg name "$ENTRY_NAME" '.entries[]?|select(.name==$name)|.id' "$META" 2>/dev/null|head -1)"; fi local uuid; if [[ -n "$existing_uuid" ]]; then uuid="$existing_uuid"; else uuid="$(uuidgen | tr 'A-Z' 'a-z')"; fi local tmp="${META}.tmp" content if [[ -f "$META" ]]; then content="$(jq --arg id "$uuid" --arg name "$ENTRY_NAME" '.appliedId=$id|.entries=((.entries//[])|map(select(.name!=$name))+[{id:$id,name:$name}])' "$META")" else content="$(jq -n --arg id "$uuid" --arg name "$ENTRY_NAME" '{appliedId:$id,entries:[{id:$id,name:$name}]}')"; fi printf '%s' "$content" > "$tmp"; chmod 600 "$tmp"; mv "$tmp" "$META"; printf '%s' "$uuid" } restart_claude() { killall Claude 2>/dev/null || true; sleep 1; open -a Claude; } BASE_URL="${SF_BASE_URL:-https://api.siliconflow.cn/}" preflight; SF_KEY="$(resolve_api_key)" confirm_or_abort "configure Claude Desktop to use SiliconFlow ($BASE_URL, $MAIN_MODEL) and restart." UUID="$(ensure_meta_entry)"; write_entry "$UUID"; restart_claude cat <<'EOF' Done. Claude Desktop is restarting with SiliconFlow as the inference backend. Re-run sfclaude-desktop any time to refresh the gateway config. EOF