- Simplify model display names (remove verbose descriptions) - Add use-model.sh for quick profile switching (dev-corp/ornith/fable/sonnet/opus) - Change default gateway port from 18443 to 18444 - Remove deprecated ANTHROPIC_CUSTOM_MODEL_OPTION env vars - Support system/developer role messages in OpenAI conversion - Remove gateway-models.json cache (no longer needed) - Add environment variables for gateway port and PATH in launchd plist
263 lines
7.8 KiB
Bash
Executable file
263 lines
7.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
CLAUDE_DIR="$HOME/.claude"
|
|
SETTINGS="$CLAUDE_DIR/settings.json"
|
|
GATEWAY="$CLAUDE_DIR/iop-claude-gateway.py"
|
|
DEV_CORP_KEY_HELPER="$CLAUDE_DIR/dev-corp-openai-key.sh"
|
|
USE_MODEL="$CLAUDE_DIR/use-model.sh"
|
|
LABEL="com.iop.agent-client.claude"
|
|
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
|
|
PYTHON_BIN="/usr/bin/python3"
|
|
UID_VALUE="$(id -u)"
|
|
GATEWAY_PORT="${IOP_CLAUDE_GATEWAY_PORT:-18444}"
|
|
|
|
if [[ ! -x "$PYTHON_BIN" ]]; then
|
|
PYTHON_BIN="$(command -v python3 || true)"
|
|
fi
|
|
|
|
if [[ -z "$PYTHON_BIN" || ! -x "$PYTHON_BIN" ]]; then
|
|
echo "python3 를 찾을 수 없습니다."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -x "$CLAUDE_DIR/anthropic_key.sh" ]]; then
|
|
echo "~/.claude/anthropic_key.sh 설정이 먼저 필요합니다."
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$CLAUDE_DIR/cache" "$HOME/Library/LaunchAgents"
|
|
|
|
OLD_LABELS=(
|
|
"$LABEL"
|
|
"com.leedongmyung.dev-corp-claude-gateway"
|
|
"com.seulgivibe.claude.models"
|
|
"com.seulgivibe.claude.anthropic-proxy"
|
|
"com.leedongmyung.claude.seulgivibe-anthropic-proxy"
|
|
)
|
|
|
|
for old_label in "${OLD_LABELS[@]}"; do
|
|
launchctl bootout "gui/$UID_VALUE/$old_label" >/dev/null 2>&1 || true
|
|
done
|
|
|
|
OLD_PLISTS=(
|
|
"$PLIST"
|
|
"$HOME/Library/LaunchAgents/com.leedongmyung.dev-corp-claude-gateway.plist"
|
|
"$HOME/Library/LaunchAgents/com.seulgivibe.claude.models.plist"
|
|
"$HOME/Library/LaunchAgents/com.seulgivibe.claude.anthropic-proxy.plist"
|
|
"$HOME/Library/LaunchAgents/com.leedongmyung.claude.seulgivibe-anthropic-proxy.plist"
|
|
)
|
|
|
|
for old_plist in "${OLD_PLISTS[@]}"; do
|
|
rm -f "$old_plist"
|
|
done
|
|
|
|
for old_process in \
|
|
"$CLAUDE_DIR/iop-claude-gateway.py" \
|
|
"$CLAUDE_DIR/seulgivibe-claude-gateway.py" \
|
|
"$CLAUDE_DIR/dev-corp-claude-gateway.py" \
|
|
"$CLAUDE_DIR/seulgivibe-anthropic-proxy.mjs"
|
|
do
|
|
pids="$(pgrep -f "$old_process" || true)"
|
|
if [[ -n "$pids" ]]; then
|
|
while IFS= read -r pid; do
|
|
[[ -n "$pid" && "$pid" != "$$" ]] && kill "$pid" >/dev/null 2>&1 || true
|
|
done <<< "$pids"
|
|
fi
|
|
done
|
|
|
|
rm -f "$CLAUDE_DIR/seulgivibe-anthropic-proxy.mjs"
|
|
install -m 755 "$SCRIPT_DIR/iop-claude-gateway.py" "$GATEWAY"
|
|
|
|
if [[ ! -x "$DEV_CORP_KEY_HELPER" ]]; then
|
|
cat > "$DEV_CORP_KEY_HELPER" <<'EOF'
|
|
#!/bin/sh
|
|
set -eu
|
|
|
|
if [ -f "$HOME/.claude/dev-corp-openai-api-key" ]; then
|
|
tr -d '\n' < "$HOME/.claude/dev-corp-openai-api-key"
|
|
exit 0
|
|
fi
|
|
|
|
if [ -f "$HOME/.pi/agent/dev-corp-openai-api-key" ]; then
|
|
tr -d '\n' < "$HOME/.pi/agent/dev-corp-openai-api-key"
|
|
exit 0
|
|
fi
|
|
|
|
exec "$HOME/.claude/anthropic_key.sh"
|
|
EOF
|
|
chmod 755 "$DEV_CORP_KEY_HELPER"
|
|
fi
|
|
|
|
"$PYTHON_BIN" - "$SETTINGS" "$CLAUDE_DIR" "$GATEWAY_PORT" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
import time
|
|
|
|
settings_path = pathlib.Path(sys.argv[1])
|
|
claude_dir = pathlib.Path(sys.argv[2])
|
|
gateway_port = sys.argv[3]
|
|
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
settings = {}
|
|
if settings_path.exists():
|
|
backup = settings_path.with_name(f"{settings_path.name}.bak-{time.strftime('%Y%m%d%H%M%S')}")
|
|
backup.write_bytes(settings_path.read_bytes())
|
|
try:
|
|
settings = json.loads(settings_path.read_text() or "{}")
|
|
except Exception:
|
|
settings = {}
|
|
|
|
base_url = f"http://127.0.0.1:{gateway_port}/anthropic"
|
|
common_env = {
|
|
"ANTHROPIC_BASE_URL": base_url,
|
|
"NODE_TLS_REJECT_UNAUTHORIZED": "0",
|
|
"CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1",
|
|
}
|
|
removed_env_keys = [
|
|
"ANTHROPIC_CUSTOM_MODEL_OPTION",
|
|
"ANTHROPIC_CUSTOM_MODEL_OPTION_NAME",
|
|
"ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION",
|
|
"ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES",
|
|
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
|
"ANTHROPIC_DEFAULT_FABLE_MODEL_NAME",
|
|
"ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION",
|
|
]
|
|
model_aliases = {
|
|
"claude-seulgivibe-sonnet-4-5": "sonnet",
|
|
"claude-seulgivibe-opus-4-8": "opus",
|
|
"gemma4:26b": "claude-dev-corp-gemma4-26b",
|
|
"ornith35b": "claude-dev-corp-ornith35b",
|
|
"ornith:35b": "claude-dev-corp-ornith35b",
|
|
}
|
|
|
|
def normalize_settings(value, model=None):
|
|
output = dict(value or {})
|
|
output["apiKeyHelper"] = output.get("apiKeyHelper") or "~/.claude/anthropic_key.sh"
|
|
env = dict(output.get("env") or {})
|
|
for key in removed_env_keys:
|
|
env.pop(key, None)
|
|
env.update(common_env)
|
|
output["env"] = env
|
|
output.pop("availableModels", None)
|
|
output.pop("enforceAvailableModels", None)
|
|
if model is not None:
|
|
output["model"] = model
|
|
elif output.get("model") in model_aliases:
|
|
output["model"] = model_aliases[output["model"]]
|
|
output.setdefault("permissions", {})
|
|
output["permissions"].setdefault("defaultMode", "bypassPermissions")
|
|
output["permissions"].setdefault("skipDangerousModePermissionPrompt", True)
|
|
output.setdefault("effortLevel", "xhigh")
|
|
output.setdefault("alwaysThinkingEnabled", True)
|
|
output.setdefault("skipDangerousModePermissionPrompt", True)
|
|
return output
|
|
|
|
settings = normalize_settings(settings)
|
|
settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=False) + "\n")
|
|
|
|
profiles = {
|
|
"settings.dev-corp.json": "claude-dev-corp-gemma4-26b",
|
|
"settings.ornith35b.json": "claude-dev-corp-ornith35b",
|
|
"settings.fable.json": "claude-seulgivibe-mythos-5",
|
|
"settings.sonnet-4-5.json": "sonnet",
|
|
"settings.opus-4-8.json": "opus",
|
|
}
|
|
for filename, model in profiles.items():
|
|
profile_path = claude_dir / filename
|
|
profile = {}
|
|
if profile_path.exists():
|
|
try:
|
|
profile = json.loads(profile_path.read_text() or "{}")
|
|
except Exception:
|
|
profile = {}
|
|
profile = normalize_settings(profile, model=model)
|
|
profile_path.write_text(json.dumps(profile, indent=2, ensure_ascii=False) + "\n")
|
|
PY
|
|
|
|
cat > "$USE_MODEL" <<'EOF'
|
|
#!/bin/sh
|
|
set -eu
|
|
|
|
profile="${1:-}"
|
|
case "$profile" in
|
|
dev-corp|devcorp|gemma|gemma4)
|
|
source_file="$HOME/.claude/settings.dev-corp.json"
|
|
;;
|
|
ornith|ornith35b)
|
|
source_file="$HOME/.claude/settings.ornith35b.json"
|
|
;;
|
|
fable)
|
|
source_file="$HOME/.claude/settings.fable.json"
|
|
;;
|
|
sonnet|sonnet-4-5)
|
|
source_file="$HOME/.claude/settings.sonnet-4-5.json"
|
|
;;
|
|
opus|opus-4-8)
|
|
source_file="$HOME/.claude/settings.opus-4-8.json"
|
|
;;
|
|
*)
|
|
echo "Usage: $0 dev-corp|ornith|fable|sonnet|opus" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
mkdir -p "$HOME/.claude/backups"
|
|
if [ -f "$HOME/.claude/settings.json" ]; then
|
|
cp "$HOME/.claude/settings.json" "$HOME/.claude/backups/settings.json.$(date +%Y%m%d-%H%M%S)"
|
|
fi
|
|
|
|
cp "$source_file" "$HOME/.claude/settings.json"
|
|
|
|
if command -v jq >/dev/null 2>&1; then
|
|
jq -r '"Claude Code model set to " + .model + " / effort=" + (.effortLevel // "default")' "$HOME/.claude/settings.json"
|
|
else
|
|
echo "Claude Code model profile applied: $profile"
|
|
fi
|
|
EOF
|
|
chmod 755 "$USE_MODEL"
|
|
rm -f "$CLAUDE_DIR/cache/gateway-models.json"
|
|
|
|
cat > "$PLIST" <<EOF
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>Label</key>
|
|
<string>$LABEL</string>
|
|
<key>ProgramArguments</key>
|
|
<array>
|
|
<string>$PYTHON_BIN</string>
|
|
<string>$GATEWAY</string>
|
|
</array>
|
|
<key>EnvironmentVariables</key>
|
|
<dict>
|
|
<key>IOP_CLAUDE_GATEWAY_PORT</key>
|
|
<string>$GATEWAY_PORT</string>
|
|
<key>PATH</key>
|
|
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
</dict>
|
|
<key>RunAtLoad</key>
|
|
<true/>
|
|
<key>KeepAlive</key>
|
|
<true/>
|
|
<key>StandardOutPath</key>
|
|
<string>$CLAUDE_DIR/iop-claude-gateway.out.log</string>
|
|
<key>StandardErrorPath</key>
|
|
<string>$CLAUDE_DIR/iop-claude-gateway.err.log</string>
|
|
</dict>
|
|
</plist>
|
|
EOF
|
|
|
|
launchctl bootstrap "gui/$UID_VALUE" "$PLIST"
|
|
launchctl kickstart -k "gui/$UID_VALUE/$LABEL"
|
|
|
|
for _ in 1 2 3 4 5; do
|
|
curl -fsS "http://127.0.0.1:$GATEWAY_PORT/health" >/dev/null 2>&1 && break
|
|
sleep 1
|
|
done
|
|
|
|
curl -fsS "http://127.0.0.1:$GATEWAY_PORT/anthropic/v1/models" >/dev/null
|
|
echo "완료: Claude TUI를 새로 열고 /model 에서 모델을 선택하세요."
|