From f3a58e1c6d2c3bf0419921e427ad082339e7b34e Mon Sep 17 00:00:00 2001 From: bookworm Date: Fri, 17 Apr 2026 00:22:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20v2.3.0=20-=20Phase=204=20=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E7=94=A8=E6=88=B7=E7=9B=B4=E6=8E=A5=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E4=B8=AD=E8=BD=AC=E7=AB=99=E5=87=AD=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Win: Show-ApiKeyDialog GUI + Test-ApiKey 验证 Mac: 调用 ~/.claude/change-key.js (统一逻辑) 两者都保留旧授权码流程作为向后兼容 Co-Authored-By: Claude Opus 4.7 (1M context) --- Bookworm-OneClick-Mac.sh | 4 +- Bookworm-Setup.sh | 33 ++++++++- auto-setup.ps1 | 147 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 176 insertions(+), 8 deletions(-) diff --git a/Bookworm-OneClick-Mac.sh b/Bookworm-OneClick-Mac.sh index 251e1d2..b5dde34 100644 --- a/Bookworm-OneClick-Mac.sh +++ b/Bookworm-OneClick-Mac.sh @@ -1,6 +1,6 @@ #!/bin/bash # ============================================================ -# Bookworm Smart Assistant - macOS 全自动安装 v2.2.4 +# Bookworm Smart Assistant - macOS 全自动安装 v2.3.0 # # 用法 (任选一种): # 方式1: 下载后运行 @@ -39,7 +39,7 @@ echo " | _ \\ / _ \\ / _ \\| |/ /\\ \\ /\\ / / _ \\| '__| '\`_ \` _ \\" echo " | |_) | (_) | (_) | < \\ V V / (_) | | | | | | | |" echo " |____/ \\___/ \\___/|_|\\_\\ \\_/\\_/ \\___/|_| |_| |_| |_|" echo "" -echo -e " ${BOLD}全自动安装 v2.2.4 — macOS${NC}" +echo -e " ${BOLD}全自动安装 v2.3.0 — macOS${NC}" echo -e " ${BLUE}92 Skills | 18 Agents | 34 Hooks${NC}" echo -e "${NC}" diff --git a/Bookworm-Setup.sh b/Bookworm-Setup.sh index a1f828f..ff0a64f 100644 --- a/Bookworm-Setup.sh +++ b/Bookworm-Setup.sh @@ -35,7 +35,7 @@ banner() { echo " | |_) | (_) | (_) | < \\ V V / (_) | | | | | | | |" echo " |____/ \\___/ \\___/|_|\\_\\ \\_/\\_/ \\___/|_| |_| |_| |_|" echo "" - echo -e " ${BOLD}Portable macOS Setup v2.2.4${NC}" + echo -e " ${BOLD}Portable macOS Setup v2.3.0${NC}" echo -e " ${BLUE}92 Skills | 18 Agents | 34 Hooks${NC}" echo -e "${NC}" } @@ -321,7 +321,36 @@ parse_authcode() { # 先尝试缓存 if load_cached_secrets 2>/dev/null; then : # 缓存加载成功 -elif [ -f "$SECRETS_ENC" ] || ls "$BOOT_DIR"/secrets-*.enc 2>/dev/null | head -1 | grep -q .; then +else + # 优先级 3: 调用 change-key.js 让用户直接输入中转站凭证 (v2.3) + CHANGE_KEY_JS="$CLAUDE_DIR/change-key.js" + if [ -f "$CHANGE_KEY_JS" ] && command -v node &>/dev/null; then + echo "" + info "配置中转站凭证 (中转站: https://bww.letcareme.com)" + for attempt in 1 2 3; do + echo "" + read -rs -p " 粘贴凭证 (第 $attempt/3 次, 留空跳过): " UCRED + echo "" + [ -z "$UCRED" ] && { warn "已跳过"; break; } + if node "$CHANGE_KEY_JS" "$UCRED" 2>&1; then + export ANTHROPIC_API_KEY="$UCRED" + export ANTHROPIC_BASE_URL="https://bww.letcareme.com" + UCRED="" + save_secrets_to_cache + success "凭证已写入并缓存" + echo "" + info "换凭证方式: 重跑安装器 / bash ~/.claude/change-key.sh / Claude Code 里 /change-key" + break + else + UCRED="" + [ $attempt -lt 3 ] && warn "验证失败, 剩余 $((3-attempt)) 次" || fail "3 次失败" + fi + done + fi +fi + +# 优先级 4: 授权码模式 (向后兼容旧用户) +if [ -z "$ANTHROPIC_API_KEY" ] && { [ -f "$SECRETS_ENC" ] || ls "$BOOT_DIR"/secrets-*.enc 2>/dev/null | head -1 | grep -q .; }; then DECRYPTED="" valid_attempts=0 total_attempts=0 diff --git a/auto-setup.ps1 b/auto-setup.ps1 index f870d62..39eb168 100644 --- a/auto-setup.ps1 +++ b/auto-setup.ps1 @@ -16,7 +16,7 @@ param( $ErrorActionPreference = "Stop" # ─── 版本号 (每次更新递增, build.ps1 自动读取) ────── -$BWVersion = "2.2.4" +$BWVersion = "2.3.0" # ─── B4: 单实例保护 (防止双击两次导致竞态) ───────── $mutexCreated = $false @@ -352,6 +352,105 @@ function Show-AuthCodeDialog($attempt = 1, $maxAttempts = 3) { return $null } +# ─── 中转站 API Key 输入对话框 + 验证 (v2.3 新增) ── +function Show-ApiKeyDialog($attempt = 1, $maxAttempts = 3, $existingKey = "") { + $form = New-Object System.Windows.Forms.Form + $form.Text = "Bookworm - 中转站 API Key ($attempt/$maxAttempts)" + $form.Size = New-Object System.Drawing.Size(520, 280) + $form.StartPosition = "CenterScreen" + $form.FormBorderStyle = "FixedDialog" + $form.MaximizeBox = $false + $form.TopMost = $true + $form.BackColor = [System.Drawing.Color]::White + + $lblInfo = New-Object System.Windows.Forms.Label + $lblInfo.Location = New-Object System.Drawing.Point(20, 15) + $lblInfo.Size = New-Object System.Drawing.Size(470, 50) + $lblInfo.Text = "请粘贴你的中转站 API Key`n(bww.letcareme.com 注册后在后台获取, sk- 开头)" + $lblInfo.Font = New-Object System.Drawing.Font("Segoe UI", 9) + $form.Controls.Add($lblInfo) + + $keyBox = New-Object System.Windows.Forms.TextBox + $keyBox.Location = New-Object System.Drawing.Point(20, 75) + $keyBox.Size = New-Object System.Drawing.Size(470, 30) + $keyBox.Font = New-Object System.Drawing.Font("Consolas", 10) + $keyBox.Text = $existingKey + $keyBox.PasswordChar = '*' + $form.Controls.Add($keyBox) + + $chkShow = New-Object System.Windows.Forms.CheckBox + $chkShow.Location = New-Object System.Drawing.Point(20, 110) + $chkShow.Size = New-Object System.Drawing.Size(150, 25) + $chkShow.Text = "显示 Key" + $chkShow.Add_CheckedChanged({ if ($chkShow.Checked) { $keyBox.PasswordChar = [char]0 } else { $keyBox.PasswordChar = '*' } }) + $form.Controls.Add($chkShow) + + $lblHint = New-Object System.Windows.Forms.Label + $lblHint.Location = New-Object System.Drawing.Point(20, 140) + $lblHint.Size = New-Object System.Drawing.Size(470, 40) + $lblHint.Text = "首次使用请先注册并充值:`nhttps://bww.letcareme.com" + $lblHint.Font = New-Object System.Drawing.Font("Segoe UI", 8) + $lblHint.ForeColor = [System.Drawing.Color]::FromArgb(100, 110, 130) + $form.Controls.Add($lblHint) + + $btnOK = New-Object System.Windows.Forms.Button + $btnOK.Location = New-Object System.Drawing.Point(280, 195) + $btnOK.Size = New-Object System.Drawing.Size(100, 35) + $btnOK.Text = "验证并保存" + $btnOK.DialogResult = [System.Windows.Forms.DialogResult]::OK + $form.AcceptButton = $btnOK + $form.Controls.Add($btnOK) + + $btnCancel = New-Object System.Windows.Forms.Button + $btnCancel.Location = New-Object System.Drawing.Point(390, 195) + $btnCancel.Size = New-Object System.Drawing.Size(100, 35) + $btnCancel.Text = "取消" + $btnCancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel + $form.CancelButton = $btnCancel + $form.Controls.Add($btnCancel) + + $form.Add_Shown({ $keyBox.Focus() }) + $result = $form.ShowDialog() + + if ($result -eq [System.Windows.Forms.DialogResult]::OK) { + return $keyBox.Text.Trim() + } + return $null +} + +# 向中转站发一个最小请求验证 key 是否可用 +function Test-ApiKey([string]$apiKey, [string]$baseUrl = "https://bww.letcareme.com") { + if (-not $apiKey -or $apiKey.Length -lt 10) { return $false } + try { + $body = '{"model":"claude-sonnet-4-5","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}' + $req = [System.Net.WebRequest]::Create("$baseUrl/v1/messages") + $req.Method = "POST" + $req.ContentType = "application/json" + $req.Headers["x-api-key"] = $apiKey + $req.Headers["anthropic-version"] = "2023-06-01" + $req.Timeout = 10000 + $bytes = [System.Text.Encoding]::UTF8.GetBytes($body) + $req.ContentLength = $bytes.Length + $stream = $req.GetRequestStream() + $stream.Write($bytes, 0, $bytes.Length) + $stream.Close() + $resp = $req.GetResponse() + $resp.Close() + return $true + } catch [System.Net.WebException] { + # 401/403 = 认证失败, 其他网络错误也返回 false + $statusCode = 0 + try { $statusCode = [int]$_.Exception.Response.StatusCode } catch {} + # 如果返回 200/400 说明 key 有效 (400 可能是请求体问题, 但 key 本身通过) + if ($statusCode -ge 200 -and $statusCode -lt 500 -and $statusCode -ne 401 -and $statusCode -ne 403) { + return $true + } + return $false + } catch { + return $false + } +} + function Show-GiteaCredentialDialog { $form = New-Object System.Windows.Forms.Form $form.Text = "Bookworm - Gitea 登录" @@ -1017,12 +1116,52 @@ if (-not $secretsDecrypted -and (Get-CachedSecrets)) { Log-OK "从 Registry 缓存加载凭证" $secretsDecrypted = $true } -# 优先级 3: 解密 (缓存均未命中时) +# 优先级 3: 直接输入中转站 API Key (v2.3 新增) +if (-not $secretsDecrypted) { + Show-MsgBox "欢迎使用 Bookworm Portable!`n`n接下来需要配置你的中转站 API Key。`n`n如果还没有, 请先去 https://bww.letcareme.com 注册并充值获取。" "配置 API Key" "OK" "Information" + + $keyAttempts = 0 + $maxKeyAttempts = 3 + while ($keyAttempts -lt $maxKeyAttempts) { + $keyAttempts++ + $apiKey = Show-ApiKeyDialog $keyAttempts $maxKeyAttempts + if (-not $apiKey) { + Log-Warn "用户取消 API Key 输入" + break + } + # 基础格式校验 + if ($apiKey.Length -lt 20) { + Show-MsgBox "API Key 格式错误 (长度过短)。`n请检查后重试。" "格式错误" "OK" "Warning" + continue + } + Log-Info "正在验证 API Key..." + if (Test-ApiKey $apiKey) { + # 验证通过, 写入环境变量 + 缓存 + [System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $apiKey, "Process") + [System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $apiKey, "User") + [System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", "https://bww.letcareme.com", "Process") + [System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", "https://bww.letcareme.com", "User") + Log-OK "API Key 已验证并写入环境变量 (永久生效)" + $secretsDecrypted = $true + Save-SecretsToCache + Show-MsgBox "API Key 验证成功!`n`n已写入系统环境变量, 任何终端输入 claude 即可启动。`n`n以后想换 Key, 可以:`n1. 双击桌面 '更换Key.bat'`n2. 或 Claude Code 里输入 /change-key`n3. 或重跑安装器" "验证成功" "OK" "Information" + break + } else { + $remaining = $maxKeyAttempts - $keyAttempts + if ($remaining -gt 0) { + Show-MsgBox "API Key 验证失败 (无法连接或认证错误)。`n剩余重试: $remaining 次`n`n请检查:`n1. Key 是否正确 (sk- 开头)`n2. 中转站是否有余额`n3. 网络和代理是否正常" "验证失败" "OK" "Warning" + } else { + Show-MsgBox "3 次验证均失败。`n`n请检查 Key 和网络, 或联系管理员。" "验证失败" "OK" "Error" + } + } + } +} + +# 优先级 4: 解密授权码 (向后兼容, 旧用户保留) if (-not $secretsDecrypted) { $cryptoHelper = Join-Path $BootDir "crypto-helper.js" if (-not (Test-Cmd "node") -or -not (Test-Path $cryptoHelper)) { - Log-Fail "解密需要 Node.js (Phase 1 应已安装)" - Show-MsgBox "解密凭证需要 Node.js,但未检测到。`n请确认 Phase 1 安装成功后重试。" "缺少 Node.js" "OK" "Error" + Log-Info "跳过授权码解密" } elseif ((Test-Path $SecretsEnc) -or (Get-ChildItem $BootDir -Filter "secrets-*.enc" -ErrorAction SilentlyContinue)) { # 强制要求授权码 — 不允许跳过 (跳过 = 无法使用)