diff --git a/README.md b/README.md index d223b7b..49baa88 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ exec bash - `ai-claude` - запускает оригинальный Claude Code через `claude`. - `ai-gpt` - запускает нативный OpenAI Codex CLI, при отсутствии пытается поставить его через `https://chatgpt.com/codex/install.sh`. - `ai-deepseek` - запускает Claude Code через DeepSeek Anthropic-compatible API, проверяет и сохраняет DeepSeek API key. -- `ai-kimi` - запускает нативный Kimi Code CLI, при отсутствии пытается поставить его через `https://code.kimi.com/kimi-code/install.sh`, настраивает Artemox provider и модель `kimi-k2.6`. +- `ai-kimi` - запускает Claude Code через официальный Kimi Code API (`https://api.kimi.com/coding/`), проверяет и сохраняет Kimi API key. - `ai-gemini` - запускает нативный Antigravity CLI `agy`, при отсутствии пытается поставить его через `https://antigravity.google/cli/install.sh`. Для `ai-gemini` скрипт в конце отдельно предупреждает использовать отдельный Google-аккаунт. @@ -64,8 +64,7 @@ exec bash ## Ключи и конфиги - DeepSeek key хранится в `~/.config/ai-setup/deepseek_key` с правами `600`. -- Artemox/Kimi key хранится в `~/.config/ai-setup/kimi_key` с правами `600`. -- Kimi config пишется в `${KIMI_CODE_HOME:-$HOME/.kimi-code}/config.toml`. +- Kimi key хранится в `~/.config/ai-setup/kimi_key` с правами `600`. - Исходник глобальных правил лежит в `GLOBAL_RULES.md`. - При запуске глобальные правила пишутся в `~/.config/ai-setup/global_rules.md`. @@ -85,8 +84,7 @@ exec bash Лаунчеры запускают CLI в максимально свободном режиме: - `ai-gpt` использует `--dangerously-bypass-approvals-and-sandbox`. -- `ai-claude`, `ai-deepseek` и `ai-gemini` используют `--dangerously-skip-permissions`. -- `ai-kimi` использует `--yolo`. +- `ai-claude`, `ai-deepseek`, `ai-kimi` и `ai-gemini` используют `--dangerously-skip-permissions`. Это удобно для локального coding workflow, но это не sandbox для недоверенного кода. diff --git a/ai-setup.sh b/ai-setup.sh index 99e0810..9a383eb 100755 --- a/ai-setup.sh +++ b/ai-setup.sh @@ -549,182 +549,71 @@ chmod +x "$BIN_DIR/ai-deepseek" # === ai-kimi === cat > "$BIN_DIR/ai-kimi" << 'KIMIEOF' #!/usr/bin/env bash -# ai-kimi - запуск нативного Kimi Code через Artemox API +# ai-kimi - запуск Claude Code через официальный Kimi Code API +source ~/.local/bin/ai-api-helpers.sh -source "$HOME/.local/bin/ai-api-helpers.sh" 2>/dev/null || true - -kimi_bin="$HOME/.kimi-code/bin/kimi" -[ ! -f "$kimi_bin" ] && kimi_bin="$(command -v kimi 2>/dev/null)" - -if [ -z "$kimi_bin" ] || [ ! -f "$kimi_bin" ]; then - echo "Kimi Code не найден. Устанавливаю..." - curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash - kimi_bin="$HOME/.kimi-code/bin/kimi" - [ ! -f "$kimi_bin" ] && kimi_bin="$(command -v kimi 2>/dev/null)" -fi - -if [ -z "$kimi_bin" ] || [ ! -f "$kimi_bin" ]; then - echo "Ошибка: не удалось установить Kimi Code." - exit 1 -fi - -case "${1:-}" in - --version|-V|--help|-h) - exec "$kimi_bin" "$@" - ;; -esac - -config_file="${KIMI_CODE_HOME:-$HOME/.kimi-code}/config.toml" key_file="$HOME/.config/ai-setup/kimi_key" -model_alias="artemox/kimi-k2.6" -model_name="kimi-k2.6" -base_url="https://api.artemox.com/v1" +api_key="" +reauth=0 -_extract_artemox_key() { - [ -f "$config_file" ] || return 0 - python3 - "$config_file" <<'PYEOF' -import re -import sys +[ -f "$key_file" ] && api_key=$(cat "$key_file") -path = sys.argv[1] -try: - lines = open(path, encoding="utf-8").read().splitlines() -except OSError: - raise SystemExit(0) - -inside = False -for line in lines: - stripped = line.strip() - if re.match(r'^\[providers\.(?:"artemox"|artemox)\]$', stripped): - inside = True - continue - if inside and stripped.startswith("["): - inside = False - if inside: - match = re.match(r'^api_key\s*=\s*"(.*)"\s*$', stripped) - if match: - print(match.group(1)) - break -PYEOF -} - -_write_artemox_config() { - mkdir -p "$(dirname "$config_file")" - ARTEMOX_API_KEY="$api_key" python3 - "$config_file" <<'PYEOF' -import json -import os -import re -import sys - -path = sys.argv[1] -api_key = os.environ["ARTEMOX_API_KEY"] - -try: - content = open(path, encoding="utf-8").read() -except OSError: - content = "" - -lines = content.splitlines() -filtered = [] -skip = False -for line in lines: - stripped = line.strip() - if re.match(r'^\[providers\.(?:"artemox"|artemox)\]$', stripped): - skip = True - continue - if re.match(r'^\[models\."artemox/kimi-k2\.6"\]$', stripped): - skip = True - continue - if skip and stripped.startswith("["): - skip = False - if skip: - continue - if re.match(r'^\s*default_model\s*=', line): - continue - filtered.append(line) - -body = "\n".join(filtered).strip() -managed = f'''default_model = "artemox/kimi-k2.6" - -# Managed by ai-kimi. Re-run ai-kimi to refresh Artemox settings. -[providers.artemox] -type = "openai" -base_url = "https://api.artemox.com/v1" -api_key = {json.dumps(api_key)} - -[models."artemox/kimi-k2.6"] -provider = "artemox" -model = "kimi-k2.6" -max_context_size = 262144 -capabilities = ["thinking", "tool_use"] -''' - -new_content = managed -if body: - new_content += "\n" + body + "\n" - -with open(path, "w", encoding="utf-8") as fh: - fh.write(new_content) -PYEOF - chmod 600 "$config_file" -} - -_prompt_artemox_key() { - echo "Получить/проверить ключ: https://artemox.com/dashboard" - if [ ! -t 0 ]; then - echo "Artemox API ключ не найден. Запустите ai-kimi в интерактивном терминале и введите ключ." +if [ -n "$api_key" ]; then + echo -n "Проверка сохранённого Kimi ключа... " + _claude_test_api "https://api.kimi.com/coding/v1/messages" "x-api-key: $api_key" "kimi-k2.6" + _handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://www.kimi.com/code" + ret=$? + if [ $ret -eq 401 ]; then + rm -f "$key_file" + api_key="" + reauth=1 + elif [ $ret -eq 429 ]; then + echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " + read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac + elif [ $ret -ne 0 ]; then exit 1 fi - read -r -p "Введите ваш Artemox API ключ: " api_key - [ -z "$api_key" ] && { echo "Выход."; exit 1; } -} - -api_key="" -[ -f "$key_file" ] && api_key=$(cat "$key_file") -[ -z "$api_key" ] && api_key=$(_extract_artemox_key) -[ -z "$api_key" ] && _prompt_artemox_key - -if declare -F _claude_test_openai_api >/dev/null && declare -F _handle_openai_api_response >/dev/null; then - echo -n "Проверка Artemox ключа... " - _claude_test_openai_api "$base_url/chat/completions" "$api_key" "$model_name" - _handle_openai_api_response "Artemox/Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://artemox.com/dashboard" - - case "$_CLAUDE_TEST_CODE" in - 200|000) - ;; - 401|403) - rm -f "$key_file" - api_key="" - echo "Сохранённый Artemox ключ недействителен." - _prompt_artemox_key - echo -n "Повторная проверка Artemox ключа... " - _claude_test_openai_api "$base_url/chat/completions" "$api_key" "$model_name" - _handle_openai_api_response "Artemox/Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://artemox.com/dashboard" - case "$_CLAUDE_TEST_CODE" in - 200|000) ;; - *) echo "Ключ НЕ сохранён."; exit 1 ;; - esac - ;; - 429) - echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " - read -r _ans - case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac - ;; - *) - echo "Ключ НЕ сохранён." - exit 1 - ;; - esac fi -mkdir -p "$(dirname "$key_file")" -echo "$api_key" > "$key_file" -chmod 600 "$key_file" -_write_artemox_config -echo "Kimi настроен на Artemox: $model_alias" +if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then + echo -n "Хотите ввести новый Kimi ключ? [Y/n] " + read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac +fi -_build_ai_sys_prompt > /dev/null # сохраняет в ~/.kimi-code/AGENTS.md (kimi читает авто) -exec "$kimi_bin" --yolo "$@" +if [ -z "$api_key" ]; then + echo "Получить ключ: https://www.kimi.com/code" + read -r -p "Введите ваш Kimi API ключ: " api_key + [ -z "$api_key" ] && { echo "Выход."; exit 1; } + + echo -n "Проверяю ключ и баланс... " + _claude_test_api "https://api.kimi.com/coding/v1/messages" "x-api-key: $api_key" "kimi-k2.6" + _handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://www.kimi.com/code" + ret=$? + if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then + mkdir -p "$(dirname "$key_file")" + echo "$api_key" > "$key_file" + chmod 600 "$key_file" + echo "Ключ сохранён." + if [ $ret -eq 429 ]; then + echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " + read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac + fi + else + echo "Ключ НЕ сохранён." + exit 1 + fi +fi + +SYS_PROMPT=$(_build_ai_sys_prompt) +ANTHROPIC_BASE_URL=https://api.kimi.com/coding \ +ANTHROPIC_AUTH_TOKEN="$api_key" \ +ANTHROPIC_MODEL=kimi-k2.6 \ +ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.6 \ +ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.6 \ +ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6 \ +CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \ +CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ +claude --dangerously-skip-permissions --system-prompt "$SYS_PROMPT" "$@" KIMIEOF chmod +x "$BIN_DIR/ai-kimi" @@ -795,7 +684,7 @@ echo "Доступные команды (теперь это независим echo -e " ${CYAN}ai-claude${NC} - Оригинальный Claude Code (Anthropic)" echo -e " ${CYAN}ai-gpt${NC} - OpenAI Codex (нативный CLI, автоустановка)" echo -e " ${CYAN}ai-deepseek${NC} - DeepSeek (API ключ сохраняется)" -echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.6 (нативный CLI, автоустановка)" +echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.6 (через Claude Code, API ключ сохраняется)" echo -e " ${CYAN}ai-gemini${NC} - Gemini (нативный agy CLI, автоустановка)" echo "" echo -e "${YELLOW}⚠️ Для Gemini используйте отдельный Google-аккаунт!${NC}" diff --git a/test_isolated.sh b/test_isolated.sh index 1868c1e..4171e6c 100644 --- a/test_isolated.sh +++ b/test_isolated.sh @@ -51,29 +51,14 @@ else fi echo "" -echo "=== Test 2: ai-kimi calls kimi install URL when missing ===" -MOCK_LOG="$TMPDIR/mock_curl2.log" +echo "=== Test 2: ai-kimi is a Claude Code launcher with Kimi backend ===" -cat > "$TMPDIR/curl" << CURLEOF -#!/usr/bin/env bash -echo "\$@" >> "$MOCK_LOG" -if [[ "\$*" == *"code.kimi.com/kimi-code/install.sh"* ]]; then - mkdir -p "\$HOME/.kimi-code/bin" - echo '#!/usr/bin/env bash -echo "kimi mock \$*"' > "\$HOME/.kimi-code/bin/kimi" - chmod +x "\$HOME/.kimi-code/bin/kimi" -fi -CURLEOF -chmod +x "$TMPDIR/curl" - -HOME="$TMPDIR" PATH="$TMPDIR:/usr/bin:/bin" "$BIN_DIR/ai-kimi" --version 2>&1 || true - -if grep -q "code.kimi.com/kimi-code/install.sh" "$MOCK_LOG"; then - echo "[PASS] ai-kimi вызвал установку Kimi" +if grep -q 'ANTHROPIC_BASE_URL' "$BIN_DIR/ai-kimi" \ + && grep -q 'api.kimi.com/coding' "$BIN_DIR/ai-kimi" \ + && grep -q 'claude --dangerously-skip-permissions' "$BIN_DIR/ai-kimi"; then + echo "[PASS] ai-kimi запускает claude с официальным Kimi API" else - echo "[FAIL] ai-kimi НЕ вызвал установку Kimi" - echo "curl log:" - cat "$MOCK_LOG" 2>/dev/null || echo "(пусто)" + echo "[FAIL] ai-kimi должен запускать claude с api.kimi.com/coding" exit 1 fi diff --git a/tests/test_fixes.sh b/tests/test_fixes.sh index 41a98a6..f3dabcf 100755 --- a/tests/test_fixes.sh +++ b/tests/test_fixes.sh @@ -35,32 +35,33 @@ test_gpt_no_proxy() { fi } -# ── ai-kimi: auto-install kimi ──────────────────────────────────────────── -test_kimi_autoinstall() { - if echo "$KIMI_SECTION" | grep -q 'curl -fsSL https://code.kimi.com/kimi-code/install.sh'; then - ok "ai-kimi: auto-installs kimi via official install script" +# ── ai-kimi: launches claude with Kimi backend ──────────────────────────── +test_kimi_claude_launcher() { + if echo "$KIMI_SECTION" | grep -q 'claude --dangerously-skip-permissions' \ + && echo "$KIMI_SECTION" | grep -q 'ANTHROPIC_BASE_URL'; then + ok "ai-kimi: launches claude with ANTHROPIC_BASE_URL set" else - fail "ai-kimi: missing kimi auto-install" + fail "ai-kimi: must launch claude with ANTHROPIC_BASE_URL" fi } -# ── ai-kimi: no proxy logic (simplified launcher) ───────────────────────── -test_kimi_no_proxy() { - if echo "$KIMI_SECTION" | grep -q 'ANTHROPIC_BASE_URL'; then - fail "ai-kimi: still contains proxy logic (ANTHROPIC_BASE_URL)" +# ── ai-kimi: uses official Kimi API ────────────────────────────────────── +test_kimi_official_api() { + if echo "$KIMI_SECTION" | grep -q 'api.kimi.com/coding' \ + && echo "$KIMI_SECTION" | grep -q 'ANTHROPIC_MODEL=kimi-k2.6' \ + && ! echo "$KIMI_SECTION" | grep -q 'artemox'; then + ok "ai-kimi: uses official Kimi API and model" else - ok "ai-kimi: proxy logic removed (no ANTHROPIC_BASE_URL)" + fail "ai-kimi: must use official Kimi API (api.kimi.com/coding) and model kimi-k2.6" fi } -# ── ai-kimi: Artemox config generation ─────────────────────────────────── -test_kimi_artemox_config() { - if echo "$KIMI_SECTION" | grep -q 'api.artemox.com/v1' \ - && echo "$KIMI_SECTION" | grep -q 'config.toml' \ - && echo "$KIMI_SECTION" | grep -q 'default_model = "artemox/kimi-k2.6"'; then - ok "ai-kimi: configures Artemox provider and kimi-k2.6 model" +# ── ai-kimi: no artemox or config.toml logic ───────────────────────────── +test_kimi_no_artemox() { + if echo "$KIMI_SECTION" | grep -q 'config.toml' || echo "$KIMI_SECTION" | grep -q 'artemox'; then + fail "ai-kimi: still contains Artemox or config.toml logic" else - fail "ai-kimi: missing Artemox config generation" + ok "ai-kimi: Artemox and config.toml logic removed" fi } @@ -129,9 +130,9 @@ test_script_syntax() { test_script_syntax test_gpt_autoinstall test_gpt_no_proxy -test_kimi_autoinstall -test_kimi_no_proxy -test_kimi_artemox_config +test_kimi_claude_launcher +test_kimi_official_api +test_kimi_no_artemox test_gemini_native_launcher test_global_rules_include_quality_guidelines test_native_rule_files_generated