feat: поддержка API-ключей в add/switch-account, не только Claude.ai

Хуки add-account и switch-account теперь ветвятся по AI_LAUNCHER:
claude - циклический обход сохранённых Claude.ai аккаунтов,
kimi - добавление и переключение API-ключей по кругу.
Skills обновлены под "account or API key".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 05:21:12 +03:00
parent 2632f4af11
commit 7a5a977aca
4 changed files with 220 additions and 68 deletions

View File

@@ -8,9 +8,15 @@
input=$(cat) input=$(cat)
prompt=$(echo "$input" | jq -r '.user_prompt // .prompt // empty' 2>/dev/null) prompt=$(echo "$input" | jq -r '.user_prompt // .prompt // empty' 2>/dev/null)
normalized=$(echo "$prompt" | sed 's|^[[:space:]]*/||; s|[[:space:]]*$||') normalized=$(echo "$prompt" | sed 's|^[[:space:]]*/||; s|[[:space:]]*$||')
# Для /add-account ключ передаётся в том же сообщении, поэтому проверяем префикс.
normalized_cmd=$(echo "$normalized" | awk '{print $1}')
[ "$normalized" != "add-account" ] && exit 0 [ "$normalized_cmd" != "add-account" ] && exit 0
LAUNCHER="${AI_LAUNCHER:-claude}"
case "$LAUNCHER" in
claude)
CREDS="$HOME/.claude/.credentials.json" CREDS="$HOME/.claude/.credentials.json"
ACCOUNTS_DIR="$HOME/.claude/accounts" ACCOUNTS_DIR="$HOME/.claude/accounts"
CURRENT_FILE="$ACCOUNTS_DIR/current" CURRENT_FILE="$ACCOUNTS_DIR/current"
@@ -45,3 +51,101 @@ disown
# exit 0: Claude загружает скилл add-account и говорит что делать # exit 0: Claude загружает скилл add-account и говорит что делать
exit 0 exit 0
;;
kimi)
KEYS_DIR="$HOME/.config/ai-setup/kimi_keys"
CURRENT_FILE="$KEYS_DIR/current"
mkdir -p "$KEYS_DIR"
# Убедиться, что текущий ключ сохранён под своим alias.
current=$(cat "$CURRENT_FILE" 2>/dev/null || true)
if [ -n "$current" ] && [ -f "$KEYS_DIR/${current}.key" ]; then
chmod 600 "$KEYS_DIR/${current}.key"
fi
# Ключ передаётся в том же сообщении: /add-account <key>
new_key=$(echo "$prompt" | sed 's|^[[:space:]]*/add-account[[:space:]]*||; s|[[:space:]]*$||')
if [ -z "$new_key" ]; then
echo "" >&2
echo "Укажите Kimi API ключ в том же сообщении: /add-account sk-..." >&2
exit 2
fi
# Проверка ключа.
echo -n "Проверяю Kimi ключ... " >&2
response=$(curl -s -w "\n%{http_code}" --max-time 15 \
"https://api.kimi.com/coding/v1/messages" \
-H "x-api-key: $new_key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"kimi-k2.7","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}' \
2>/dev/null || echo "000")
code=$(echo "$response" | tail -1)
case "$code" in
200|400|429)
echo "OK" >&2
;;
401|403)
echo "" >&2
echo "Ошибка: ключ недействителен (HTTP $code)." >&2
exit 2
;;
000)
echo "" >&2
echo "Не удалось проверить ключ (сеть недоступна?). Попробуйте позже." >&2
exit 2
;;
*)
echo "" >&2
echo "Ошибка при проверке ключа (HTTP $code)." >&2
exit 2
;;
esac
# Генерация следующего свободного alias.
i=1
while [ -f "$KEYS_DIR/account${i}.key" ]; do
i=$((i + 1))
done
alias_name="account${i}"
echo "$new_key" > "$KEYS_DIR/${alias_name}.key"
chmod 600 "$KEYS_DIR/${alias_name}.key"
# Пытаемся получить email/имя аккаунта Kimi для статусной строки.
_kimi_account_info() {
local api_key="$1"
local resp email name
for url in "https://api.kimi.com/coding/v1/account" "https://api.kimi.com/coding/v1/users/me"; do
resp=$(curl -s --max-time 10 "$url" \
-H "x-api-key: $api_key" \
-H "Accept: application/json" \
2>/dev/null || echo "")
[ -z "$resp" ] && continue
email=$(echo "$resp" | jq -r '.email // .data.email // .account.email // empty' 2>/dev/null)
name=$(echo "$resp" | jq -r '.name // .data.name // .account.name // empty' 2>/dev/null)
if [ -n "$email" ]; then echo "$email"; return 0; fi
if [ -n "$name" ]; then echo "$name"; return 0; fi
done
return 1
}
meta=$(_kimi_account_info "$new_key" 2>/dev/null || true)
if [ -n "$meta" ]; then
echo "$meta" > "$KEYS_DIR/${alias_name}.meta"
chmod 600 "$KEYS_DIR/${alias_name}.meta"
fi
echo "$alias_name" > "$CURRENT_FILE"
echo "Новый Kimi ключ сохранён как: $alias_name. ai-kimi перезапустится с ним." >&2
exit 0
;;
*)
exit 0
;;
esac

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# UserPromptSubmit hook: перехватывает /switch-account и циклически меняет аккаунт. # UserPromptSubmit hook: перехватывает /switch-account и циклически меняет аккаунт/ключ.
# Текущий аккаунт определяется ПО ТОКЕНУ в .credentials.json (account-email.sh), # Текущий аккаунт определяется ПО ТОКЕНУ в .credentials.json (account-email.sh),
# а не по claude auth status — он читает рассинхронизированный oauthAccount. # а не по claude auth status — он читает рассинхронизированный oauthAccount.
# На Linux Claude Code перечитывает .credentials.json на лету: новый аккаунт # На Linux Claude Code перечитывает .credentials.json на лету: новый аккаунт
@@ -13,6 +13,10 @@ normalized=$(echo "$prompt" | sed 's|^[[:space:]]*/||; s|[[:space:]]*$||')
[ "$normalized" != "switch-account" ] && exit 0 [ "$normalized" != "switch-account" ] && exit 0
LAUNCHER="${AI_LAUNCHER:-claude}"
case "$LAUNCHER" in
claude)
ACCOUNTS_DIR="$HOME/.claude/accounts" ACCOUNTS_DIR="$HOME/.claude/accounts"
CREDS="$HOME/.claude/.credentials.json" CREDS="$HOME/.claude/.credentials.json"
CURRENT_FILE="$ACCOUNTS_DIR/current" CURRENT_FILE="$ACCOUNTS_DIR/current"
@@ -52,3 +56,43 @@ chmod 600 "$CREDS"
echo "$next" > "$CURRENT_FILE" echo "$next" > "$CURRENT_FILE"
exit 0 exit 0
;;
kimi)
KEYS_DIR="$HOME/.config/ai-setup/kimi_keys"
CURRENT_FILE="$KEYS_DIR/current"
mkdir -p "$KEYS_DIR"
current=$(cat "$CURRENT_FILE" 2>/dev/null || true)
# Если current указывает в никуда, но есть ключи — сбросить на первый попавшийся.
if [ -n "$current" ] && [ ! -f "$KEYS_DIR/${current}.key" ]; then
current=""
fi
mapfile -t keys < <(ls "$KEYS_DIR"/*.key 2>/dev/null \
| xargs -I{} basename {} .key | sort)
if [ ${#keys[@]} -le 1 ]; then
echo "Только один Kimi ключ (${current:-нет}). Добавь второй через /add-account." >&2
exit 2
fi
idx=-1
for i in "${!keys[@]}"; do
[ "${keys[$i]}" = "$current" ] && idx=$i && break
done
next_idx=$(( (idx + 1) % ${#keys[@]} ))
next="${keys[$next_idx]}"
echo "$next" > "$CURRENT_FILE"
echo "Kimi ключ переключён на: $next. ai-kimi перезапустится с новым ключом." >&2
exit 0
;;
*)
exit 0
;;
esac

View File

@@ -1,8 +1,12 @@
--- ---
name: add-account name: add-account
description: Add a new Claude.ai account (handled by UserPromptSubmit hook, no LLM needed) description: Add a new account or API key (handled by UserPromptSubmit hook, no LLM needed)
--- ---
Хук сохранил текущий аккаунт и открыл браузер для логина нового. Ответь ТОЛЬКО этим текстом (без markdown, без лишних слов): Хук уже обработал запрос. Ответь ТОЛЬКО этим текстом (без markdown, без лишних слов):
Браузер открыт — авторизуйся там. После авторизации новый аккаунт сохранится автоматически и сразу станет активным (Claude Code на Linux перечитывает токен на лету). /switch-account переключает между всеми сохранёнными аккаунтами по кругу. Для Claude.ai: браузер открыт — авторизуйся там. После авторизации новый аккаунт сохранится автоматически и сразу станет активным (Claude Code на Linux перечитывает токен на лету).
Для Kimi: укажи API-ключ в том же сообщении: `/add-account sk-...`. Хук проверит и сохранит ключ; ai-kimi перезапустится с ним.
/switch-account переключает между всеми сохранёнными аккаунтами/ключами по кругу.

View File

@@ -1,8 +1,8 @@
--- ---
name: switch-account name: switch-account
description: Switch to next Claude.ai account (handled by UserPromptSubmit hook, no LLM needed) description: Switch to next account or API key (handled by UserPromptSubmit hook, no LLM needed)
--- ---
Переключение аккаунта уже выполнено хуком до того, как ты это читаешь. Переключение аккаунта/ключа уже выполнено хуком до того, как ты это читаешь.
Ответь ровно одним символом: `✓` Ответь ровно одним символом: `✓`
Никаких инструментов. Никаких объяснений. Только `✓`. Никаких инструментов. Никаких объяснений. Только `✓`.