diff --git a/Bookworm-OneClick-Mac.sh b/Bookworm-OneClick-Mac.sh index 264492a..cc760de 100644 --- a/Bookworm-OneClick-Mac.sh +++ b/Bookworm-OneClick-Mac.sh @@ -277,7 +277,7 @@ if ! grep -q "$ALIAS_MARKER" "$SHELL_RC" 2>/dev/null; then # Bookworm Portable aliases alias bw='cd ~/bookworm-boot && NO_PROXY="bww.letcareme.com,code.letcareme.com,localhost,127.0.0.1" claude' -alias bw-update='cd ~/bookworm-boot && git pull && echo "Updated!"' +alias bw-update='cd ~/bookworm-boot && git pull && cd ~/.claude && git pull && echo "Updated!"' ALIASES success "已添加到 $SHELL_RC:" info " bw — 启动 Bookworm" diff --git a/Bookworm-Setup.sh b/Bookworm-Setup.sh index 4cb5f88..58a735a 100644 --- a/Bookworm-Setup.sh +++ b/Bookworm-Setup.sh @@ -198,11 +198,73 @@ for d in debug sessions cache backups telemetry memory projects; do done # ============================================================ -# Step 4: 解密凭证 +# Step 4: 解密凭证 (含 Keychain 本日免密) # ============================================================ step 4 "解密凭证" -if [ -f "$SECRETS_ENC" ] && [ -n "$OPENSSL_CMD" ]; then +# Keychain 缓存相关 +KEYCHAIN_SERVICE="bookworm-secrets" +KEYCHAIN_ACCOUNT="$(whoami)" +CACHE_LOADED=false + +# 尝试从 Keychain 加载缓存 +load_cached_secrets() { + local cached + cached=$(security find-generic-password -s "$KEYCHAIN_SERVICE" -a "$KEYCHAIN_ACCOUNT" -w 2>/dev/null) || return 1 + # 检查是否过期 (缓存格式: EXPIRY=ISO日期\nKEY=VALUE\n...) + local expiry + expiry=$(echo "$cached" | head -1) + local expiry_date="${expiry#EXPIRY=}" + local today + today=$(date +%Y-%m-%d) + if [ "$expiry_date" != "$today" ]; then + # 已过期,删除缓存 + security delete-generic-password -s "$KEYCHAIN_SERVICE" -a "$KEYCHAIN_ACCOUNT" 2>/dev/null || true + return 1 + fi + # 加载环境变量 (跳过 EXPIRY 行) + local count=0 + while IFS= read -r line; do + [ -z "$line" ] && continue + [[ "$line" == EXPIRY=* ]] && continue + local key="${line%%=*}" + local value="${line#*=}" + key=$(echo "$key" | tr -d ' ') + if [ -n "$key" ] && [ -n "$value" ]; then + export "$key=$value" + count=$((count + 1)) + fi + done <<< "$cached" + if [ $count -gt 0 ] && [ -n "$ANTHROPIC_API_KEY" ]; then + success "从 Keychain 缓存加载 $count 个凭证 (免密)" + CACHE_LOADED=true + return 0 + fi + return 1 +} + +# 保存凭证到 Keychain +save_secrets_to_cache() { + local today + today=$(date +%Y-%m-%d) + local data="EXPIRY=$today" + local env_keys="ANTHROPIC_API_KEY ANTHROPIC_BASE_URL GITHUB_PERSONAL_ACCESS_TOKEN SLACK_BOT_TOKEN ATLASSIAN_API_TOKEN BROWSERBASE_API_KEY FIRECRAWL_API_KEY" + for k in $env_keys; do + local v="${!k}" + if [ -n "$v" ]; then + data="$data +$k=$v" + fi + done + security add-generic-password -s "$KEYCHAIN_SERVICE" -a "$KEYCHAIN_ACCOUNT" -w "$data" -U 2>/dev/null && \ + success "凭证已缓存至今日 23:59 (下次免密)" || \ + warn "Keychain 缓存失败 (不影响使用)" +} + +# 先尝试缓存 +if load_cached_secrets 2>/dev/null; then + : # 缓存加载成功 +elif [ -f "$SECRETS_ENC" ] && [ -n "$OPENSSL_CMD" ]; then DECRYPTED="" for attempt in 1 2 3; do echo "" @@ -222,6 +284,13 @@ if [ -f "$SECRETS_ENC" ] && [ -n "$OPENSSL_CMD" ]; then fi done <<< "$DECRYPTED" DECRYPTED="" + + # 询问是否缓存 + echo "" + read -p " 今日内免密启动? (y/n): " CACHE_CHOICE + if [ "$CACHE_CHOICE" = "y" ] || [ "$CACHE_CHOICE" = "Y" ]; then + save_secrets_to_cache + fi break else if [ $attempt -lt 3 ]; then diff --git a/quick-reference.txt b/quick-reference.txt index 20f5965..39c3fce 100644 --- a/quick-reference.txt +++ b/quick-reference.txt @@ -47,7 +47,7 @@ # [1/6] 前置检查 (缺依赖自动提示 winget 安装) # [2/6] 代理自动检测 (无需手动找端口) # [3/6] 解密凭证 (输入主密码, 最多3次重试, 可选本日免密) - # [4/6] 同步配置 (git clone 92 Skills / 18 Agents / 29 Hooks) + # [4/6] 同步配置 (git clone 92 Skills / 18 Agents / 34 Hooks) # [5/6] 渲染模板 + 初始化 + Bookworm 完整性验证 + MCP 检查 # [6/6] 启动 Claude Code diff --git a/quick-start.html b/quick-start.html index 75960ec..4c7d043 100644 --- a/quick-start.html +++ b/quick-start.html @@ -74,7 +74,7 @@

Bookworm Portable 日常速查卡

-
v1.5 | 92 Skills / 18 Agents / 29 Hooks
打印后贴在显示器旁边
+
v1.5 | 92 Skills / 18 Agents / 34 Hooks
打印后贴在显示器旁边
diff --git a/sync-version.js b/sync-version.js new file mode 100644 index 0000000..c38950c --- /dev/null +++ b/sync-version.js @@ -0,0 +1,94 @@ +#!/usr/bin/env node +/** + * sync-version.js — 从 stats-compiled.json 同步版本号到 boot 仓库文件 + * + * 用法: node sync-version.js [--dry-run] + * + * 替换规则: + * {N} Skills → 从 stats.summary.skills + * {N} Agents → 从 stats.summary.agents + * {N} Hooks → 从 stats.summary.hooks (settings.json 注册的总数) + */ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const DRY_RUN = process.argv.includes('--dry-run'); +const BOOT_DIR = __dirname; +const STATS_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.claude', 'stats-compiled.json'); + +// 读取 stats +let stats; +try { + stats = JSON.parse(fs.readFileSync(STATS_PATH, 'utf8')); +} catch (e) { + console.error(` [FAIL] 无法读取 ${STATS_PATH}: ${e.message}`); + process.exit(1); +} + +const skills = stats.summary.skills; +const agents = stats.summary.agents; +const hooks = stats.summary.hooks; +const version = stats.summary?.version || stats.version || 'v6.5.1'; + +console.log(` sync-version: ${skills} Skills / ${agents} Agents / ${hooks} Hooks (${version})`); + +// 需要更新的文件 (相对于 boot 仓库) +const FILES = [ + 'Bookworm-Setup.sh', + 'Bookworm-Setup.bat', + 'Bookworm-OneClick.bat', + 'Bookworm-OneClick-Win10.bat', + 'Bookworm-OneClick-Mac.sh', + 'install.ps1', + 'guide-mac.html', + 'quick-start.html', + 'quick-reference.txt', +]; + +// 替换模式: 匹配 "数字 Skills"、"数字 Agents"、"数字 Hooks" +// 支持多种分隔格式: "92 Skills"、"92 个 Skills"、"92 Skills" +const REPLACEMENTS = [ + // 纯文本格式: "92 Skills"、"92 个 Skills" + { pattern: /\b\d+ Skills/g, replacement: `${skills} Skills` }, + { pattern: /\b\d+ Agents/g, replacement: `${agents} Agents` }, + { pattern: /\b\d+ Hooks/g, replacement: `${hooks} Hooks` }, + { pattern: /\b\d+ 个 Skills/g, replacement: `${skills} 个 Skills` }, + // HTML badge 格式: 92 Skills + { pattern: /\d+<\/strong> Skills/g, replacement: `${skills} Skills` }, + { pattern: /\d+<\/strong> Agents/g, replacement: `${agents} Agents` }, + { pattern: /\d+<\/strong> Hooks/g, replacement: `${hooks} Hooks` }, + // PS1 格式: "92 Skills / 18 Agents / 34 Hooks" + { pattern: /\b\d+ Skills \/ \d+ Agents \/ \d+ Hooks/g, replacement: `${skills} Skills / ${agents} Agents / ${hooks} Hooks` }, + // bat 格式: "92 Skills / 18 Agents" + { pattern: /\b\d+ Skills \/ \d+ Agents/g, replacement: `${skills} Skills / ${agents} Agents` }, + // "Bookworm (92 Skills)" / "Bookworm - 92 Skills" + { pattern: /Bookworm \(\d+ Skills\)/g, replacement: `Bookworm (${skills} Skills)` }, + { pattern: /Bookworm - \d+ Skills/g, replacement: `Bookworm - ${skills} Skills` }, +]; + +let totalChanged = 0; + +for (const file of FILES) { + const filePath = path.join(BOOT_DIR, file); + if (!fs.existsSync(filePath)) continue; + + let content = fs.readFileSync(filePath, 'utf8'); + const original = content; + + for (const { pattern, replacement } of REPLACEMENTS) { + content = content.replace(pattern, replacement); + } + + if (content !== original) { + if (!DRY_RUN) { + fs.writeFileSync(filePath, content, 'utf8'); + } + totalChanged++; + console.log(` ${DRY_RUN ? '[DRY] ' : ''}更新: ${file}`); + } +} + +console.log(` 同步完成: ${totalChanged}/${FILES.length} 个文件更新`); +if (DRY_RUN) console.log(' [DRY RUN] 未实际写入');