feat(ai-setup): /logout для ai-glm, ai-deepseek, ai-kimi
Новый 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/<current>.key
(+.meta) с пересчётом current; exit 2 глушит нативную /logout для
api-key провайдеров
- 3 скилла logout-{glm,deepseek,kimi} для автокомплита
- ai-setup.sh: секция деплоя 6.7.3 + хук в копирование/регистрацию
изолированных cfg
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
60
home-configs/claude/hooks/logout-account-hook.sh
Executable file
60
home-configs/claude/hooks/logout-account-hook.sh
Executable file
@@ -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
|
||||||
8
home-configs/claude/skills/logout-deepseek/SKILL.md
Normal file
8
home-configs/claude/skills/logout-deepseek/SKILL.md
Normal file
@@ -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`.
|
||||||
8
home-configs/claude/skills/logout-glm/SKILL.md
Normal file
8
home-configs/claude/skills/logout-glm/SKILL.md
Normal file
@@ -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`.
|
||||||
8
home-configs/claude/skills/logout-kimi/SKILL.md
Normal file
8
home-configs/claude/skills/logout-kimi/SKILL.md
Normal file
@@ -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`.
|
||||||
@@ -557,6 +557,41 @@ else
|
|||||||
warn "Файл $ADD_HOOK_SRC не найден, пропускаю"
|
warn "Файл $ADD_HOOK_SRC не найден, пропускаю"
|
||||||
fi
|
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 ──
|
# ── 6.8. Регистрация официального маркетплейса плагинов Claude ──
|
||||||
info "Настраиваю маркетплейс плагинов Claude Code..."
|
info "Настраиваю маркетплейс плагинов Claude Code..."
|
||||||
if ! command -v claude &>/dev/null; then
|
if ! command -v claude &>/dev/null; then
|
||||||
@@ -989,16 +1024,16 @@ PYEOF
|
|||||||
# ai-claude использует ~/.claude/hooks напрямую; остальные лаунчеры получают
|
# ai-claude использует ~/.claude/hooks напрямую; остальные лаунчеры получают
|
||||||
# свою копию, чтобы /switch-account и /add-account работали изолированно.
|
# свою копию, чтобы /switch-account и /add-account работали изолированно.
|
||||||
mkdir -p "$cfg/hooks"
|
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
|
if [ -f "$HOME/.claude/hooks/$hook" ]; then
|
||||||
cp "$HOME/.claude/hooks/$hook" "$cfg/hooks/$hook"
|
cp "$HOME/.claude/hooks/$hook" "$cfg/hooks/$hook"
|
||||||
chmod +x "$cfg/hooks/$hook"
|
chmod +x "$cfg/hooks/$hook"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Регистрируем UserPromptSubmit хуки в изолированном settings.json
|
# Регистрируем 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
|
import sys, json, os
|
||||||
settings_path, switch_hook, add_hook = sys.argv[1:4]
|
settings_path = sys.argv[1]
|
||||||
data = {}
|
data = {}
|
||||||
if os.path.exists(settings_path):
|
if os.path.exists(settings_path):
|
||||||
try:
|
try:
|
||||||
@@ -1008,7 +1043,7 @@ if os.path.exists(settings_path):
|
|||||||
pass
|
pass
|
||||||
data.setdefault("hooks", {}).setdefault("UserPromptSubmit", [{"hooks": []}])
|
data.setdefault("hooks", {}).setdefault("UserPromptSubmit", [{"hooks": []}])
|
||||||
ups = data["hooks"]["UserPromptSubmit"]
|
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}"'
|
hook_cmd = f'bash "{hook_path}"'
|
||||||
already = any(
|
already = any(
|
||||||
any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))
|
any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))
|
||||||
|
|||||||
Reference in New Issue
Block a user