From c379ee8efcc75d5e617d296bcc51b03fb5ec67a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=D1=82=D0=B5=D0=BD=D0=BA=D0=BE?= Date: Tue, 14 Jul 2026 10:10:14 +0300 Subject: [PATCH] =?UTF-8?q?feat(ai-setup):=20/logout=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20ai-glm,=20ai-deepseek,=20ai-kimi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новый UserPromptSubmit хук logout-account-hook.sh перехватывает /logout (провайдер по AI_LAUNCHER) и /logout-glm|/logout-deepseek|/logout-kimi (явно), удаляет сохранённый ключ провайдера. Для ai-claude /logout отступает — работает нативный oauth-logout. - хук: удаляет glm_key/deepseek_key или текущий kimi_keys/.key (+.meta) с пересчётом current; exit 2 глушит нативную /logout для api-key провайдеров - 3 скилла logout-{glm,deepseek,kimi} для автокомплита - ai-setup.sh: секция деплоя 6.7.3 + хук в копирование/регистрацию изолированных cfg Co-Authored-By: Claude --- .../claude/hooks/logout-account-hook.sh | 60 +++++++++++++++++++ .../claude/skills/logout-deepseek/SKILL.md | 8 +++ .../claude/skills/logout-glm/SKILL.md | 8 +++ .../claude/skills/logout-kimi/SKILL.md | 8 +++ scripts/ai-setup.sh | 43 +++++++++++-- 5 files changed, 123 insertions(+), 4 deletions(-) create mode 100755 home-configs/claude/hooks/logout-account-hook.sh create mode 100644 home-configs/claude/skills/logout-deepseek/SKILL.md create mode 100644 home-configs/claude/skills/logout-glm/SKILL.md create mode 100644 home-configs/claude/skills/logout-kimi/SKILL.md diff --git a/home-configs/claude/hooks/logout-account-hook.sh b/home-configs/claude/hooks/logout-account-hook.sh new file mode 100755 index 0000000..bd11321 --- /dev/null +++ b/home-configs/claude/hooks/logout-account-hook.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 Виталий Никитенко. MIT License. +# UserPromptSubmit hook: перехватывает /logout, /logout-glm, /logout-deepseek, /logout-kimi. +# Удаляет сохранённый API-ключ провайдера, чтобы при следующем запуске лаунчер +# запросил новый. +# /logout-glm | /logout-deepseek | /logout-kimi — явно указанный провайдер. +# /logout (без суффикса) — провайдер по AI_LAUNCHER (glm/deepseek/kimi). +# Для ai-claude и прочих лаунчеров /logout отступает (exit 0) — пусть работает +# нативный oauth-logout. exit 2 после удаления: LLM не нужна, и нативная +# /logout не пытается сделать бессмысленный oauth-logout для api-key провайдеров. + +input=$(cat) +prompt=$(echo "$input" | jq -r '.user_prompt // .prompt // empty' 2>/dev/null) +normalized=$(echo "$prompt" | sed 's|^[[:space:]]*/||; s|[[:space:]]*$||') +cmd=$(echo "$normalized" | awk '{print $1}') + +target="" +case "$cmd" in + logout-glm) target=glm ;; + logout-deepseek) target=deepseek ;; + logout-kimi) target=kimi ;; + logout) + case "${AI_LAUNCHER:-claude}" in + glm|deepseek|kimi) target="$AI_LAUNCHER" ;; + *) exit 0 ;; + esac + ;; + *) exit 0 ;; +esac + +CFG="$HOME/.config/ai-setup" + +case "$target" in + glm) + rm -f "$CFG/glm_key" + echo "✓ GLM ключ удалён. Перезапусти ai-glm, чтобы ввести новый." >&2 + ;; + deepseek) + rm -f "$CFG/deepseek_key" + echo "✓ DeepSeek ключ удалён. Перезапусти ai-deepseek, чтобы ввести новый." >&2 + ;; + kimi) + KEYS_DIR="$CFG/kimi_keys" + current=$(cat "$KEYS_DIR/current" 2>/dev/null || true) + if [ -n "$current" ]; then + rm -f "$KEYS_DIR/${current}.key" "$KEYS_DIR/${current}.meta" + fi + # Пересчёт current: переключаемся на первый оставшийся ключ либо сбрасываем. + mapfile -t keys < <(ls "$KEYS_DIR"/*.key 2>/dev/null | xargs -I{} basename {} .key | sort) + if [ "${#keys[@]}" -gt 0 ]; then + echo "${keys[0]}" > "$KEYS_DIR/current" + echo "✓ Kimi ключ (${current:-нет}) удалён. Активен: ${keys[0]}. Перезапусти ai-kimi." >&2 + else + rm -f "$KEYS_DIR/current" + echo "✓ Kimi ключ (${current:-нет}) удалён. Других ключей нет. Перезапусти ai-kimi." >&2 + fi + ;; +esac + +exit 2 diff --git a/home-configs/claude/skills/logout-deepseek/SKILL.md b/home-configs/claude/skills/logout-deepseek/SKILL.md new file mode 100644 index 0000000..2ed8019 --- /dev/null +++ b/home-configs/claude/skills/logout-deepseek/SKILL.md @@ -0,0 +1,8 @@ +--- +name: logout-deepseek +description: Удалить сохранённый DeepSeek API-ключ (handled by UserPromptSubmit hook, no LLM needed) +--- + +Хук уже удалил DeepSeek ключ (`~/.config/ai-setup/deepseek_key`). Перезапусти `ai-deepseek`, чтобы ввести новый. + +Если ты видишь этот текст — хук не сработал: удали файл вручную и перезапусти `ai-deepseek`. diff --git a/home-configs/claude/skills/logout-glm/SKILL.md b/home-configs/claude/skills/logout-glm/SKILL.md new file mode 100644 index 0000000..160fb48 --- /dev/null +++ b/home-configs/claude/skills/logout-glm/SKILL.md @@ -0,0 +1,8 @@ +--- +name: logout-glm +description: Удалить сохранённый GLM (Zhipu AI) API-ключ (handled by UserPromptSubmit hook, no LLM needed) +--- + +Хук уже удалил GLM ключ (`~/.config/ai-setup/glm_key`). Перезапусти `ai-glm`, чтобы ввести новый. + +Если ты видишь этот текст — хук не сработал: удали файл вручную и перезапусти `ai-glm`. diff --git a/home-configs/claude/skills/logout-kimi/SKILL.md b/home-configs/claude/skills/logout-kimi/SKILL.md new file mode 100644 index 0000000..f7fe5c2 --- /dev/null +++ b/home-configs/claude/skills/logout-kimi/SKILL.md @@ -0,0 +1,8 @@ +--- +name: logout-kimi +description: Удалить текущий Kimi API-ключ (handled by UserPromptSubmit hook, no LLM needed) +--- + +Хук уже удалил текущий Kimi ключ из `~/.config/ai-setup/kimi_keys/` (и переключил `current` на следующий, если он есть). Перезапусти `ai-kimi`. + +Если ты видишь этот текст — хук не сработал: удали ключ вручную и перезапусти `ai-kimi`. diff --git a/scripts/ai-setup.sh b/scripts/ai-setup.sh index 6bff583..8c8d94c 100755 --- a/scripts/ai-setup.sh +++ b/scripts/ai-setup.sh @@ -557,6 +557,41 @@ else warn "Файл $ADD_HOOK_SRC не найден, пропускаю" fi +# ── 6.7.3. Хук logout-account ─────────────────────────────────── +info "Деплою хук logout-account..." +LOGOUT_HOOK_SRC="$SCRIPT_DIR/home-configs/claude/hooks/logout-account-hook.sh" +LOGOUT_HOOK_DST="$HOME/.claude/hooks/logout-account-hook.sh" +mkdir -p "$HOME/.claude/hooks" +if [ -f "$LOGOUT_HOOK_SRC" ]; then + cp "$LOGOUT_HOOK_SRC" "$LOGOUT_HOOK_DST" + chmod +x "$LOGOUT_HOOK_DST" + # Прописываем хук в settings.json (идемпотентно) + python3 - "$HOME/.claude/settings.json" "$LOGOUT_HOOK_DST" <<'PYEOF' +import sys, json, os +settings_path, hook_path = sys.argv[1], sys.argv[2] +data = {} +if os.path.exists(settings_path): + with open(settings_path) as f: + try: data = json.load(f) + except json.JSONDecodeError: pass +data.setdefault("hooks", {}).setdefault("UserPromptSubmit", [{"hooks": []}]) +hook_cmd = f'bash "{hook_path}"' +ups = data["hooks"]["UserPromptSubmit"] +already = any( + any(h.get("command", "") == hook_cmd for h in entry.get("hooks", [])) + for entry in ups +) +if not already: + ups[0]["hooks"].append({"type": "command", "command": hook_cmd}) +with open(settings_path, "w") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + f.write("\n") +PYEOF + success "Хук logout-account установлен" +else + warn "Файл $LOGOUT_HOOK_SRC не найден, пропускаю" +fi + # ── 6.8. Регистрация официального маркетплейса плагинов Claude ── info "Настраиваю маркетплейс плагинов Claude Code..." if ! command -v claude &>/dev/null; then @@ -989,16 +1024,16 @@ PYEOF # ai-claude использует ~/.claude/hooks напрямую; остальные лаунчеры получают # свою копию, чтобы /switch-account и /add-account работали изолированно. mkdir -p "$cfg/hooks" - for hook in switch-account-hook.sh add-account-hook.sh account-email.sh; do + for hook in switch-account-hook.sh add-account-hook.sh logout-account-hook.sh account-email.sh; do if [ -f "$HOME/.claude/hooks/$hook" ]; then cp "$HOME/.claude/hooks/$hook" "$cfg/hooks/$hook" chmod +x "$cfg/hooks/$hook" fi done # Регистрируем UserPromptSubmit хуки в изолированном settings.json - python3 - "$cfg/settings.json" "$cfg/hooks/switch-account-hook.sh" "$cfg/hooks/add-account-hook.sh" <<'PYEOF' + python3 - "$cfg/settings.json" "$cfg/hooks/switch-account-hook.sh" "$cfg/hooks/add-account-hook.sh" "$cfg/hooks/logout-account-hook.sh" <<'PYEOF' import sys, json, os -settings_path, switch_hook, add_hook = sys.argv[1:4] +settings_path = sys.argv[1] data = {} if os.path.exists(settings_path): try: @@ -1008,7 +1043,7 @@ if os.path.exists(settings_path): pass data.setdefault("hooks", {}).setdefault("UserPromptSubmit", [{"hooks": []}]) ups = data["hooks"]["UserPromptSubmit"] -for hook_path in (switch_hook, add_hook): +for hook_path in sys.argv[2:]: hook_cmd = f'bash "{hook_path}"' already = any( any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))