Claude Code 安装助手

一键安装和配置 Claude Code

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Claude Code 安装助手
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  一键安装和配置 Claude Code
// @author       北枫枫
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 创建安装按钮容器
    function createInstallButton() {
        // 检查域名是否匹配 - 只允许 api.ikuncode.cc 和 h.ikuncode.cc
        const hostname = window.location.hostname;
        const allowedHosts = ['api.ikuncode.cc', 'hk.ikuncode.cc'];
        if (!allowedHosts.includes(hostname)) {
            console.log('不在允许的域名下,跳过按钮创建');
            return;
        }

        // 创建容器
        const container = document.createElement('div');
        container.id = 'claude-installer-container';
        container.style.cssText = `
            position: fixed;
            top: 13%;
            right: 20px;
            transform: translateY(-50%);
            z-index: 10000;
            display: flex;
            align-items: stretch;
            background: #007bff;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,123,255,0.3);
            overflow: hidden;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        `;

        // 创建主按钮
        const button = document.createElement('button');
        button.id = 'claude-installer-btn';
        button.textContent = '安装 Claude Code';
        button.style.cssText = `
            padding: 12px 20px;
            background: transparent;
            color: white;
            border: none;
            cursor: pointer;
            font-size: 15px;
            font-weight: 500;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            white-space: nowrap;
            overflow: hidden;
            max-width: 200px;
            opacity: 1;
        `;

        // 创建分隔线
        const divider = document.createElement('div');
        divider.id = 'claude-divider';
        divider.style.cssText = `
            width: 1px;
            background: rgba(255, 255, 255, 0.3);
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        `;

        // 创建折叠按钮
        const collapseBtn = document.createElement('button');
        collapseBtn.id = 'claude-collapse-btn';
        collapseBtn.innerHTML = '◄';
        collapseBtn.style.cssText = `
            width: 45px;
            padding: 0;
            background: transparent;
            color: white;
            border: none;
            cursor: pointer;
            font-size: 16px;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            display: flex;
            align-items: center;
            justify-content: center;
        `;

        // 悬停效果
        button.addEventListener('mouseenter', () => {
            button.style.background = 'rgba(255, 255, 255, 0.1)';
        });
        button.addEventListener('mouseleave', () => {
            button.style.background = 'transparent';
        });

        collapseBtn.addEventListener('mouseenter', () => {
            collapseBtn.style.background = 'rgba(255, 255, 255, 0.1)';
        });
        collapseBtn.addEventListener('mouseleave', () => {
            collapseBtn.style.background = 'transparent';
        });

        // 添加点击事件
        button.addEventListener('click', showInstallDialog);
        collapseBtn.addEventListener('click', toggleCollapse);

        // 组装容器
        container.appendChild(button);
        container.appendChild(divider);
        container.appendChild(collapseBtn);
        document.body.appendChild(container);
    }

    // 折叠/展开功能
    function toggleCollapse() {
        const button = document.getElementById('claude-installer-btn');
        const collapseBtn = document.getElementById('claude-collapse-btn');
        const divider = document.getElementById('claude-divider');

        if (button.style.maxWidth === '0px') {
            // 展开
            button.style.maxWidth = '200px';
            button.style.padding = '12px 20px';
            button.style.opacity = '1';
            divider.style.width = '1px';
            divider.style.opacity = '1';
            collapseBtn.innerHTML = '◄';
            collapseBtn.title = '折叠';
        } else {
            // 折叠
            button.style.maxWidth = '0px';
            button.style.padding = '12px 0';
            button.style.opacity = '0';
            divider.style.width = '0px';
            divider.style.opacity = '0';
            collapseBtn.innerHTML = '►';
            collapseBtn.title = '展开 Claude Code 安装助手';
        }
    }

    // 获取用户ID的辅助函数
    function getUserIdFromPage() {
        let userId = null;

        // 直接从localStorage中的user对象获取ID
        try {
            const userStr = localStorage.getItem('user');
            if (userStr) {
                const user = JSON.parse(userStr);
                if (user && user.id) {
                    userId = user.id.toString();
                    console.log('从localStorage[user]获取到用户ID:', userId);
                }
            }
        } catch (e) {
            console.log('解析localStorage[user]失败:', e);
        }

        return userId;
    }

    // 获取用户的API密钥
    async function fetchUserApiKeys() {
        try {
            // 检查是否在 ikuncode.cc 域名下
            if (!window.location.hostname.includes('ikuncode.cc')) {
                console.log('不在 ikuncode.cc 域名下,跳过自动获取');
                return null;
            }

            console.log('正在获取API密钥...');

            // 获取用户ID
            let userId = getUserIdFromPage();

            if (!userId) {
                console.log('未找到用户ID,无法获取API密钥');
                return null;
            }

            const headers = {
                'accept': 'application/json, text/plain, */*',
                'cache-control': 'no-store',
                'sec-fetch-dest': 'empty',
                'sec-fetch-mode': 'cors',
                'sec-fetch-site': 'same-origin',
                'veloera-user': userId
            };

            console.log('使用用户ID:', userId);

            const response = await fetch('/api/token/?p=0&size=10', {
                method: 'GET',
                headers: headers,
                credentials: 'include'
            });

            console.log('API响应状态:', response.status);

            if (response.ok) {
                const data = await response.json();
                console.log('获取到的API数据:', data);
                return data;
            } else {
                const errorText = await response.text();
                console.log('API请求失败:', response.status, response.statusText, errorText);
                return null;
            }
        } catch (error) {
            console.log('获取API密钥失败:', error);
            return null;
        }
    }

    // 显示API密钥选择器
    function showApiKeySelector(apiKeys) {
        if (!apiKeys || !apiKeys.data || apiKeys.data.length === 0) {
            return false;
        }

        const selector = document.createElement('select');
        selector.id = 'apiKeySelect';
        selector.style.cssText = `
            width: 100%;
            padding: 8px;
            margin: 10px 0;
            border: 1px solid #ddd;
            border-radius: 4px;
        `;

        // 添加默认选项
        const defaultOption = document.createElement('option');
        defaultOption.value = '';
        defaultOption.textContent = '选择API密钥';
        selector.appendChild(defaultOption);

        // 添加API密钥选项
        apiKeys.data.forEach(key => {
            const option = document.createElement('option');
            // 确保获取完整的token值
            const tokenValue = key.token || key.key || key.name || key.id;
            option.value = tokenValue;
            // 显示友好的名称和部分token
            const displayName = key.name || key.title || '未命名密钥';
            const displayToken = key.token ? key.token.substring(0, 20) + '...' : `ID: ${key.id}`;
            option.textContent = `${displayName} (${displayToken})`;

            // 添加调试信息
            console.log('添加API密钥选项:', {
                name: displayName,
                value: tokenValue,
                fullKey: key
            });

            selector.appendChild(option);
        });

        return selector;
    }

    // 显示安装对话框
    async function showInstallDialog() {
        // 先显示加载中的对话框
        const dialog = document.createElement('div');
        dialog.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 10001;
            background: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
            max-width: 500px;
            width: 90%;
        `;

        // 添加背景遮罩
        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            z-index: 10000;
        `;

        document.body.appendChild(overlay);
        document.body.appendChild(dialog);

        // 显示加载状态
        dialog.innerHTML = `
            <h3>Claude Code 安装助手</h3>
            <p>正在获取API密钥...</p>
        `;

        // 尝试获取用户的API密钥
        const apiKeys = await fetchUserApiKeys();
        const hasApiKeys = apiKeys && apiKeys.data && apiKeys.data.length > 0;

        console.log('hasApiKeys:', hasApiKeys);
        if (hasApiKeys) {
            console.log('API密钥数量:', apiKeys.data.length);
        }

        // 更新对话框内容
        dialog.innerHTML = `
            <h3>Claude Code 安装助手</h3>
            <p>此脚本将生成安装命令和配置文件</p>

            ${hasApiKeys ?
                '<label for="apiKeySelect">选择API密钥:</label>' +
                '<div id="apiKeySelectContainer"></div>' +
                '<p style="font-size: 12px; color: #666; margin: 5px 0;">或者手动输入:</p>'
                : ''
            }

            <label for="apiKey">API 令牌:</label>
            <input type="text" id="apiKey" placeholder="输入您的 ANTHROPIC_API_KEY" style="width: 100%; padding: 8px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px;">

            <label for="baseUrl">API 渠道:</label>
            <select id="baseUrl" style="width: 100%; padding: 8px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px;">
                <option value="https://api.ikuncode.cc">https://api.ikuncode.cc</option>
                <option value="https://hk.ikuncode.cc">https://hk.ikuncode.cc</option>
            </select>

            <div style="margin: 15px 0;">
                <button id="generateScript" style="padding: 10px 15px; background: #28a745; color: white; border: none; border-radius: 4px; margin-right: 10px;">生成脚本</button>
                <button id="downloadConfig" style="padding: 10px 15px; background: #17a2b8; color: white; border: none; border-radius: 4px; margin-right: 10px;">下载配置</button>
                <button id="closeDialog" style="padding: 10px 15px; background: #6c757d; color: white; border: none; border-radius: 4px;">关闭</button>
            </div>

            <div id="output" style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 4px; display: none;">
                <h4>安装步骤:</h4>
                <ol id="stepList"></ol>
            </div>
        `;

        // 如果有API密钥,添加选择器
        if (hasApiKeys) {
            const selector = showApiKeySelector(apiKeys);
            if (selector) {
                const container = dialog.querySelector('#apiKeySelectContainer');
                container.appendChild(selector);

                // 监听选择器变化
                selector.addEventListener('change', function() {
                    const apiKeyInput = document.getElementById('apiKey');
                    if (this.value) {
                        console.log('选择的API密钥值:', this.value);
                        apiKeyInput.value = this.value;
                        console.log('已填入输入框:', apiKeyInput.value);

                        // 视觉提示用户已自动填入
                        apiKeyInput.style.backgroundColor = '#e8f5e8';
                        setTimeout(() => {
                            apiKeyInput.style.backgroundColor = '';
                        }, 1000);
                    } else {
                        // 清空输入框
                        apiKeyInput.value = '';
                        console.log('已清空输入框');
                    }
                });
            }
        }

        // 事件监听
        dialog.querySelector('#generateScript').addEventListener('click', generateInstallScript);
        dialog.querySelector('#downloadConfig').addEventListener('click', downloadConfigFiles);
        dialog.querySelector('#closeDialog').addEventListener('click', () => {
            document.body.removeChild(overlay);
            document.body.removeChild(dialog);
        });
    }

    // 生成安装脚本
    function generateInstallScript() {
        const apiKey = document.getElementById('apiKey').value;
        const baseUrl = document.getElementById('baseUrl').value;

        console.log('生成脚本时使用的API密钥:', apiKey);
        console.log('生成脚本时使用的Base URL:', baseUrl);

        if (!apiKey) {
            alert('请输入 API 令牌');
            return;
        }

        const output = document.getElementById('output');
        const stepList = document.getElementById('stepList');

        stepList.innerHTML = `
            <li>运行: <code>npm install -g @anthropic-ai/claude-code</code></li>
            <li>创建目录: <code>mkdir -p ~/.claude</code></li>
            <li>脚本自动创建配置文件并写入API密钥</li>
            <li>运行: <code>claude</code></li>
        `;

        output.style.display = 'block';

        // 生成完整的安装脚本并下载
        const scriptContent = generateBashScript(apiKey, baseUrl);
        console.log('生成的脚本内容包含API密钥:', scriptContent.includes(apiKey));
        downloadFile('setup-claude.sh', scriptContent, 'text/plain');

        // 提示用户
        setTimeout(() => {
            alert(`脚本已生成并下载!

使用的API密钥: ${apiKey.substring(0, 20)}...
使用的API渠道: ${baseUrl}

运行步骤:
1. chmod +x setup-claude.sh
2. ./setup-claude.sh`);
        }, 500);
    }

    // 生成 Bash 脚本内容
    function generateBashScript(apiKey, baseUrl) {
        return `#!/bin/bash

# IKunCode Claude Code 安装脚本

set -e

echo "======================================"
echo "    IKunCode Claude Code Setup"
echo "======================================"
echo

# 使用预设的 API 令牌
ANTHROPIC_API_KEY="${apiKey}"
ANTHROPIC_BASE_URL="${baseUrl}"

if [ -z "$ANTHROPIC_API_KEY" ]; then
    echo "错误:API 令牌不能为空"
    exit 1
fi

# 首先检查 claude 命令是否存在
if ! command -v claude &> /dev/null; then
    echo "Claude Code 未安装。"

    # 检查 npm 是否存在
    if ! command -v npm &> /dev/null; then
        echo "错误:npm 未安装。"
        echo "请先安装 Node.js。"
        exit 1
    fi

    echo "发现 npm: $(npm -v)"
    echo "正在安装 Claude Code..."
    npm install -g @anthropic-ai/claude-code

    if ! command -v claude &> /dev/null; then
        echo "Claude Code 安装失败"
        exit 1
    fi
    echo "✓ Claude Code 安装成功"
else
    echo "✓ Claude Code 已安装"
fi

# 创建 .claude 目录
CLAUDE_DIR="$HOME/.claude"
if [ ! -d "$CLAUDE_DIR" ]; then
    mkdir -p "$CLAUDE_DIR"
    echo "创建目录: ~/.claude"
fi

# 创建或更新 settings.json
SETTINGS_FILE="$CLAUDE_DIR/settings.json"

if [ -f "$SETTINGS_FILE" ]; then
    echo "正在更新 settings.json..."
    # 备份现有文件
    cp "$SETTINGS_FILE" "$SETTINGS_FILE.bak"
fi

cat > "$SETTINGS_FILE" << EOF
{
  "env": {
    "DISABLE_TELEMETRY": "1",
    "ANTHROPIC_AUTH_TOKEN": "$ANTHROPIC_API_KEY",
    "ANTHROPIC_BASE_URL": "$ANTHROPIC_BASE_URL"
  }
}
EOF

echo "✓ settings.json 配置完成"

# 创建 .claude.json
CLAUDE_JSON_FILE="$HOME/.claude.json"

if [ -f "$CLAUDE_JSON_FILE" ]; then
    echo "正在更新 .claude.json..."
    # 备份现有文件
    cp "$CLAUDE_JSON_FILE" "$CLAUDE_JSON_FILE.bak"
fi

echo "✓ .claude.json 创建完成"

# 验证配置
if [ -f "$SETTINGS_FILE" ] && [ -f "$CLAUDE_JSON_FILE" ]; then
    echo
    echo "======================================"
    echo "        安装完成!"
    echo "======================================"
    echo
    echo "使用的API渠道: $ANTHROPIC_BASE_URL"
    echo
    echo "现在可以使用以下命令启动 Claude Code:"
    echo "  claude"
    echo
else
    echo "配置不完整"
    [ ! -f "$SETTINGS_FILE" ] && echo "  缺失文件: ~/.claude/settings.json"
    [ ! -f "$CLAUDE_JSON_FILE" ] && echo "  缺失文件: ~/.claude.json"
    exit 1
fi
`;
    }

    // 下载配置文件
    function downloadConfigFiles() {
        const apiKey = document.getElementById('apiKey').value;
        const baseUrl = document.getElementById('baseUrl').value;

        if (!apiKey) {
            alert('请输入 API 令牌');
            return;
        }

        // settings.json
        const settingsConfig = {
            "env": {
                "DISABLE_TELEMETRY": "1",
                "ANTHROPIC_AUTH_TOKEN": apiKey,
                "ANTHROPIC_BASE_URL": baseUrl
            }
        };

        downloadFile('settings.json', JSON.stringify(settingsConfig, null, 2), 'application/json');

        // 显示移动指令
        setTimeout(() => {
            alert(`配置文件已下载!

使用的API渠道: ${baseUrl}

请按以下步骤操作:

1. 创建 Claude 配置目录:
   mkdir -p ~/.claude

2. 将下载的 settings.json 文件移动到配置目录:
   mv ~/Downloads/settings.json ~/.claude/

3. 验证配置:
   ls -la ~/.claude/

4. 运行 Claude Code:
   claude`);
        }, 100);
    }

    // 下载文件函数
    function downloadFile(filename, content, mimeType) {
        const blob = new Blob([content], { type: mimeType });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);

        URL.revokeObjectURL(url);
    }

    // 页面加载完成后创建按钮
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', createInstallButton);
    } else {
        createInstallButton();
    }

})();