iop/agent-client/claude/install.sh

183 lines
5.4 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"
LABEL="com.iop.agent-client.claude"
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
PYTHON_BIN="/usr/bin/python3"
UID_VALUE="$(id -u)"
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.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.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/cache/gateway-models.json" <<'PY'
import json
import pathlib
import sys
import time
settings_path = pathlib.Path(sys.argv[1])
cache_path = pathlib.Path(sys.argv[2])
settings_path.parent.mkdir(parents=True, exist_ok=True)
cache_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())
settings = json.loads(settings_path.read_text() or "{}")
models = [
"claude-seulgivibe-sonnet-4-5",
"claude-seulgivibe-opus-4-8",
"claude-seulgivibe-mythos-5",
"claude-dev-corp-gemma4-26b",
]
settings["apiKeyHelper"] = settings.get("apiKeyHelper") or "~/.claude/anthropic_key.sh"
env = settings.setdefault("env", {})
env["ANTHROPIC_BASE_URL"] = "http://127.0.0.1:18443/anthropic"
env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"
env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1"
for key in [
"ANTHROPIC_CUSTOM_MODEL_OPTION",
"ANTHROPIC_CUSTOM_MODEL_OPTION_NAME",
"ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION",
"ANTHROPIC_DEFAULT_FABLE_MODEL",
"ANTHROPIC_DEFAULT_FABLE_MODEL_NAME",
"ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION",
]:
env.pop(key, None)
if settings.get("model") not in models:
settings["model"] = "claude-seulgivibe-mythos-5"
settings["availableModels"] = models
settings["enforceAvailableModels"] = True
settings.setdefault("effortLevel", "xhigh")
settings.setdefault("alwaysThinkingEnabled", True)
settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=False) + "\n")
cache = {
"baseUrl": "http://127.0.0.1:18443/anthropic",
"fetchedAt": int(time.time() * 1000),
"models": [
{"id": "claude-seulgivibe-sonnet-4-5", "display_name": "Seulgivibe Sonnet 4.5"},
{"id": "claude-seulgivibe-opus-4-8", "display_name": "Seulgivibe Opus 4.8"},
{"id": "claude-seulgivibe-mythos-5", "display_name": "Seulgivibe Fable 5"},
{"id": "claude-dev-corp-gemma4-26b", "display_name": "dev-corp gemma4:26b"},
],
}
cache_path.write_text(json.dumps(cache, ensure_ascii=False, separators=(",", ":")) + "\n")
PY
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>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:18443/health" >/dev/null 2>&1 && break
sleep 1
done
curl -fsS "http://127.0.0.1:18443/anthropic/v1/models" >/dev/null
echo "완료: Claude TUI를 새로 열고 /model 에서 모델을 선택하세요."