#!/usr/bin/env bash set -euo pipefail PI_DIR="$HOME/.pi/agent" SETTINGS="$PI_DIR/settings.json" MODELS="$PI_DIR/models.json" PYTHON_BIN="/usr/bin/python3" 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 mkdir -p "$PI_DIR" "$PYTHON_BIN" - "$SETTINGS" "$MODELS" <<'PY' import json import pathlib import subprocess import sys import time settings_path = pathlib.Path(sys.argv[1]).expanduser() models_path = pathlib.Path(sys.argv[2]).expanduser() def read_json(path): if not path.exists(): return {} return json.loads(path.read_text() or "{}") def write_json(path, value): if path.exists(): backup = path.with_name(f"{path.name}.bak-{time.strftime('%Y%m%d%H%M%S')}") backup.write_bytes(path.read_bytes()) path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n") def run_helper(path): expanded = pathlib.Path(path).expanduser() if not expanded.exists(): return "" try: result = subprocess.run( [str(expanded)], check=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, timeout=10, ) except Exception: return "" return result.stdout.strip() def merge_desired_models(existing_models, desired_models): existing_by_id = {} for existing in existing_models: if isinstance(existing, dict) and isinstance(existing.get("id"), str): existing_by_id.setdefault(existing["id"], existing) merged_models = [] for desired in desired_models: merged = dict(existing_by_id.get(desired["id"], {})) merged.update(desired) merged_models.append(merged) return merged_models def zero_cost(): return {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0} settings = read_json(settings_path) models_doc = read_json(models_path) providers = models_doc.setdefault("providers", {}) seulgivibe_provider = providers.setdefault("seulgivibe-claude", {}) seulgivibe_api_key = seulgivibe_provider.get("apiKey") or run_helper("~/.claude/anthropic_key.sh") seulgivibe_provider.update( { "baseUrl": "https://seulgivibe.lgudax.cool/anthropic", "api": "anthropic-messages", "authHeader": True, "compat": {"supportsEagerToolInputStreaming": False}, } ) if seulgivibe_api_key: seulgivibe_provider["apiKey"] = seulgivibe_api_key seulgivibe_provider["models"] = merge_desired_models( seulgivibe_provider.get("models", []), [ { "id": "claude-sonnet-4-5", "name": "Seulgivibe Claude Sonnet 4.5", "reasoning": True, "input": ["text"], "contextWindow": 200000, "maxTokens": 64000, "cost": zero_cost(), }, { "id": "claude-opus-4-8", "name": "Seulgivibe Claude Opus 4.8", "reasoning": True, "thinkingLevelMap": {"xhigh": "xhigh"}, "input": ["text"], "contextWindow": 1000000, "maxTokens": 128000, "compat": {"forceAdaptiveThinking": True, "supportsTemperature": False}, "cost": zero_cost(), }, { "id": "claude-fable-5", "name": "Seulgivibe Claude Fable 5", "reasoning": True, "thinkingLevelMap": {"xhigh": "xhigh"}, "input": ["text"], "contextWindow": 1000000, "maxTokens": 128000, "compat": {"forceAdaptiveThinking": True, "supportsTemperature": False}, "cost": zero_cost(), }, ], ) legacy_openai_provider = providers.get("seulgivibe-openai") if not isinstance(legacy_openai_provider, dict): legacy_openai_provider = {} seulgivibe_codex_provider = providers.setdefault("seulgivibe-codex", {}) seulgivibe_codex_api_key = ( seulgivibe_codex_provider.get("apiKey") or legacy_openai_provider.get("apiKey") or run_helper("~/.claude/anthropic_key.sh") ) seulgivibe_codex_provider.update( { "baseUrl": "https://seulgivibe.lgudax.cool/openai/v1", "api": "openai-responses", } ) if seulgivibe_codex_api_key: seulgivibe_codex_provider["apiKey"] = seulgivibe_codex_api_key seulgivibe_codex_provider["models"] = merge_desired_models( seulgivibe_codex_provider.get("models", []), [ { "id": "gpt-5.1", "name": "Seulgivibe Codex GPT-5.1", "reasoning": True, "thinkingLevelMap": {"off": "none"}, "input": ["text"], "contextWindow": 400000, "maxTokens": 128000, "cost": zero_cost(), }, { "id": "gpt-5.5", "name": "Seulgivibe Codex GPT-5.5", "reasoning": True, "thinkingLevelMap": {"off": "none", "xhigh": "xhigh", "minimal": None}, "input": ["text"], "contextWindow": 272000, "maxTokens": 128000, "cost": zero_cost(), }, ], ) providers.pop("seulgivibe-openai", None) settings.setdefault("defaultProvider", "seulgivibe-codex") settings.setdefault("defaultModel", "gpt-5.5") settings.setdefault("defaultThinkingLevel", "high") settings.setdefault("hideThinkingBlock", False) settings["httpIdleTimeoutMs"] = 300000 retry = settings.setdefault("retry", {}) provider_retry = retry.setdefault("provider", {}) provider_retry["timeoutMs"] = 300000 provider_retry["maxRetries"] = 0 provider_retry["maxRetryDelayMs"] = 60000 enabled = settings.setdefault("enabledModels", []) if not isinstance(enabled, list): enabled = [] desired_enabled = [ "seulgivibe-codex/gpt-5.1:medium", "seulgivibe-codex/gpt-5.5:xhigh", "seulgivibe-claude/claude-sonnet-4-5", "seulgivibe-claude/claude-opus-4-8", "seulgivibe-claude/claude-fable-5", ] normalized_enabled = [] seen_enabled = set() for item in enabled: if not isinstance(item, str): continue if item.startswith("seulgivibe-openai/"): continue if item.startswith("seulgivibe-codex/") or item.startswith("seulgivibe-claude/"): continue if item not in seen_enabled: normalized_enabled.append(item) seen_enabled.add(item) for item in desired_enabled: if item not in seen_enabled: normalized_enabled.append(item) seen_enabled.add(item) settings["enabledModels"] = normalized_enabled write_json(settings_path, settings) write_json(models_path, models_doc) PY echo "완료: Pi session에서 /reload 하거나 Pi를 재시작하세요."