From 7fd845235c524acaf856ffb654711c74a7b662c6 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Sun, 28 Jun 2026 01:50:13 +0200 Subject: [PATCH] Initial Claude launcher profiles --- .gitignore | 4 ++ README.md | 66 ++++++++++++++++++++ bin/claude-profile | 15 +++++ bin/install-claude-profiles | 24 +++++++ config/profiles.example.yml | 23 +++++++ lib/profiles.sh | 121 ++++++++++++++++++++++++++++++++++++ shell/aliases.sh | 5 ++ 7 files changed, 258 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bin/claude-profile create mode 100755 bin/install-claude-profiles create mode 100644 config/profiles.example.yml create mode 100755 lib/profiles.sh create mode 100755 shell/aliases.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8da9be9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +config/profiles.yml +*.log +.DS_Store + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3473c7 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# claude-launcher-profiles + +Portable Claude Code launcher profiles for Anthropic Pro and OpenRouter models. + +## Ziel + +Claude Code über einfache Profile starten: + +- `claude-pro` +- `claude-glm` +- `claude-kimi` +- `claude-free` +- `claude-profile ` + +Die eigentliche Konfiguration liegt in einer YAML-Datei. + +## Voraussetzungen + +- `claude` installiert +- funktionierendes Claude-Pro-Login für das `pro`-Profil +- `bash` +- `yq` zum Parsen der YAML-Datei + +Beispiel Debian/Ubuntu: + +```bash +sudo apt-get update +sudo apt-get install -y yq +``` + +## Setup + +```bash +git clone https://kitux.de/forgejo/dschlueter/claude-launcher-profiles.git +cd claude-launcher-profiles + +cp config/profiles.example.yml config/profiles.yml +$EDITOR config/profiles.yml + +chmod +x bin/claude-profile bin/install-claude-profiles lib/profiles.sh +./bin/install-claude-profiles +source ~/.bashrc +``` + +## Nutzung + +```bash +claude-pro +claude-glm +claude-kimi +claude-free +``` + +Oder generisch: + +```bash +claude-profile pro +claude-profile glm +claude-profile kimi +``` + +## Sicherheit + +`config/profiles.yml` enthält echte Keys und wird nicht committed. +Nur `config/profiles.example.yml` gehört ins Repo. + diff --git a/bin/claude-profile b/bin/claude-profile new file mode 100755 index 0000000..e8121f7 --- /dev/null +++ b/bin/claude-profile @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null +source "$SCRIPT_DIR/../lib/profiles.sh" + +PROFILE="${1:-}" +if [[ -z "$PROFILE" ]]; then + echo "Usage: claude-profile " + exit 1 +fi + +launch_profile "$PROFILE" + diff --git a/bin/install-claude-profiles b/bin/install-claude-profiles new file mode 100755 index 0000000..49322d9 --- /dev/null +++ b/bin/install-claude-profiles @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +TARGET_DIR="$HOME/src" +TARGET_REPO="$TARGET_DIR/claude-launcher-profiles" +BASHRC="$HOME/.bashrc" +ALIAS_LINE="source $TARGET_REPO/shell/aliases.sh" + +mkdir -p "$TARGET_DIR" + +if [[ "$REPO_DIR" != "$TARGET_REPO" ]]; then + if [[ ! -e "$TARGET_REPO" ]]; then + ln -s "$REPO_DIR" "$TARGET_REPO" + fi +fi + +if ! grep -Fq "$ALIAS_LINE" "$BASHRC" 2>/dev/null; then + printf '\n# claude-launcher-profiles\n%s\n' "$ALIAS_LINE" >> "$BASHRC" +fi + +echo "Installed. Run: source ~/.bashrc" + diff --git a/config/profiles.example.yml b/config/profiles.example.yml new file mode 100644 index 0000000..9a2503d --- /dev/null +++ b/config/profiles.example.yml @@ -0,0 +1,23 @@ +defaults: + claude_cmd: "claude" + openrouter_base_url: "https://openrouter.ai/api" + +profiles: + pro: + mode: "pro" + + glm: + mode: "openrouter" + openrouter_api_key: "REPLACE_ME" + model: "z-ai/glm-5.2" + + kimi: + mode: "openrouter" + openrouter_api_key: "REPLACE_ME" + model: "moonshotai/kimi-k2.7-code" + + free: + mode: "openrouter" + openrouter_api_key: "REPLACE_ME" + model: "openrouter/free" + diff --git a/lib/profiles.sh b/lib/profiles.sh new file mode 100755 index 0000000..f00e337 --- /dev/null +++ b/lib/profiles.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root() { + local script_dir + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + printf '%s\n' "$script_dir" +} + +config_file() { + printf '%s/config/profiles.yml\n' "$(repo_root)" +} + +require_yq() { + command -v yq >/dev/null 2>&1 || { + echo "Error: yq is required but not installed." >&2 + exit 1 + } +} + +require_config() { + local cfg + cfg="$(config_file)" + [[ -f "$cfg" ]] || { + echo "Error: missing config file: $cfg" >&2 + echo "Create it from config/profiles.example.yml" >&2 + exit 1 + } +} + +cfg_get() { + local expr="$1" + yq -r "$expr // \"\"" "$(config_file)" +} + +profile_exists() { + local profile="$1" + [[ "$(cfg_get ".profiles.${profile}.mode")" != "" ]] +} + +clear_claude_env() { + unset OPENROUTER_API_KEY + unset ANTHROPIC_BASE_URL + unset ANTHROPIC_AUTH_TOKEN + unset ANTHROPIC_API_KEY + unset ANTHROPIC_MODEL + unset ANTHROPIC_DEFAULT_OPUS_MODEL + unset ANTHROPIC_DEFAULT_SONNET_MODEL + unset ANTHROPIC_DEFAULT_HAIKU_MODEL + unset CLAUDE_CODE_SUBAGENT_MODEL +} + +resolve_env_ref() { + local value="$1" + + if [[ "$value" =~ ^\$[A-Za-z_][A-Za-z0-9_]*$ ]]; then + local var_name="${value:1}" + printf '%s\n' "${!var_name:-}" + else + printf '%s\n' "$value" + fi +} + +launch_profile() { + local profile="$1" + local mode claude_cmd base_url api_key model + + require_yq + require_config + + profile_exists "$profile" || { + echo "Error: profile '$profile' not found in $(config_file)" >&2 + exit 1 + } + + claude_cmd="$(cfg_get '.defaults.claude_cmd')" + [[ -n "$claude_cmd" ]] || claude_cmd="claude" + + mode="$(cfg_get ".profiles.${profile}.mode")" + + clear_claude_env + + case "$mode" in + pro) + echo "Claude Pro profile active" + exec "$claude_cmd" + ;; + openrouter) + base_url="$(cfg_get '.defaults.openrouter_base_url')" + [[ -n "$base_url" ]] || base_url="https://openrouter.ai/api" + + api_key="$(cfg_get ".profiles.${profile}.openrouter_api_key")" + api_key="$(resolve_env_ref "$api_key")" + model="$(cfg_get ".profiles.${profile}.model")" + + [[ -n "$api_key" && "$api_key" != "REPLACE_ME" ]] || { + echo "Error: openrouter_api_key missing for profile '$profile'" >&2 + exit 1 + } + + [[ -n "$model" ]] || { + echo "Error: model missing for profile '$profile'" >&2 + exit 1 + } + + export OPENROUTER_API_KEY="$api_key" + export ANTHROPIC_BASE_URL="$base_url" + export ANTHROPIC_AUTH_TOKEN="$OPENROUTER_API_KEY" + export ANTHROPIC_API_KEY="" + export ANTHROPIC_MODEL="$model" + + echo "OpenRouter profile active: $profile -> $model" + exec "$claude_cmd" + ;; + *) + echo "Error: unsupported mode '$mode' for profile '$profile'" >&2 + exit 1 + ;; + esac +} + diff --git a/shell/aliases.sh b/shell/aliases.sh new file mode 100755 index 0000000..a8f343b --- /dev/null +++ b/shell/aliases.sh @@ -0,0 +1,5 @@ +alias claude-pro="$HOME/src/claude-launcher-profiles/bin/claude-profile pro" +alias claude-glm="$HOME/src/claude-launcher-profiles/bin/claude-profile glm" +alias claude-kimi="$HOME/src/claude-launcher-profiles/bin/claude-profile kimi" +alias claude-free="$HOME/src/claude-launcher-profiles/bin/claude-profile free" +