feat: Keychain 免密 + 版本号同步 + bw-update 修复
- Bookworm-Setup.sh: macOS Keychain 本日免密缓存(对标 Windows Credential Manager) - Bookworm-OneClick-Mac.sh: bw-update 补 config 仓库更新 - sync-version.js: 新建版本号同步脚本(从 stats-compiled.json 自动注入) - quick-start.html/quick-reference.txt: Hooks 29→34 修正 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d499342271
commit
23b369f99b
@ -277,7 +277,7 @@ if ! grep -q "$ALIAS_MARKER" "$SHELL_RC" 2>/dev/null; then
|
|||||||
|
|
||||||
# Bookworm Portable aliases
|
# Bookworm Portable aliases
|
||||||
alias bw='cd ~/bookworm-boot && NO_PROXY="bww.letcareme.com,code.letcareme.com,localhost,127.0.0.1" claude'
|
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
|
ALIASES
|
||||||
success "已添加到 $SHELL_RC:"
|
success "已添加到 $SHELL_RC:"
|
||||||
info " bw — 启动 Bookworm"
|
info " bw — 启动 Bookworm"
|
||||||
|
|||||||
@ -198,11 +198,73 @@ for d in debug sessions cache backups telemetry memory projects; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Step 4: 解密凭证
|
# Step 4: 解密凭证 (含 Keychain 本日免密)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
step 4 "解密凭证"
|
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=""
|
DECRYPTED=""
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
echo ""
|
echo ""
|
||||||
@ -222,6 +284,13 @@ if [ -f "$SECRETS_ENC" ] && [ -n "$OPENSSL_CMD" ]; then
|
|||||||
fi
|
fi
|
||||||
done <<< "$DECRYPTED"
|
done <<< "$DECRYPTED"
|
||||||
DECRYPTED=""
|
DECRYPTED=""
|
||||||
|
|
||||||
|
# 询问是否缓存
|
||||||
|
echo ""
|
||||||
|
read -p " 今日内免密启动? (y/n): " CACHE_CHOICE
|
||||||
|
if [ "$CACHE_CHOICE" = "y" ] || [ "$CACHE_CHOICE" = "Y" ]; then
|
||||||
|
save_secrets_to_cache
|
||||||
|
fi
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
if [ $attempt -lt 3 ]; then
|
if [ $attempt -lt 3 ]; then
|
||||||
|
|||||||
@ -47,7 +47,7 @@
|
|||||||
# [1/6] 前置检查 (缺依赖自动提示 winget 安装)
|
# [1/6] 前置检查 (缺依赖自动提示 winget 安装)
|
||||||
# [2/6] 代理自动检测 (无需手动找端口)
|
# [2/6] 代理自动检测 (无需手动找端口)
|
||||||
# [3/6] 解密凭证 (输入主密码, 最多3次重试, 可选本日免密)
|
# [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 检查
|
# [5/6] 渲染模板 + 初始化 + Bookworm 完整性验证 + MCP 检查
|
||||||
# [6/6] 启动 Claude Code
|
# [6/6] 启动 Claude Code
|
||||||
|
|
||||||
|
|||||||
@ -74,7 +74,7 @@
|
|||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>Bookworm <span>Portable</span> 日常速查卡</h1>
|
<h1>Bookworm <span>Portable</span> 日常速查卡</h1>
|
||||||
<div class="ver">v1.5 | 92 Skills / 18 Agents / 29 Hooks<br>打印后贴在显示器旁边</div>
|
<div class="ver">v1.5 | 92 Skills / 18 Agents / 34 Hooks<br>打印后贴在显示器旁边</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ==================== 打开 PowerShell ==================== -->
|
<!-- ==================== 打开 PowerShell ==================== -->
|
||||||
|
|||||||
94
sync-version.js
Normal file
94
sync-version.js
Normal file
@ -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"、"<strong>92</strong> 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 格式: <strong>92</strong> Skills
|
||||||
|
{ pattern: /<strong>\d+<\/strong> Skills/g, replacement: `<strong>${skills}</strong> Skills` },
|
||||||
|
{ pattern: /<strong>\d+<\/strong> Agents/g, replacement: `<strong>${agents}</strong> Agents` },
|
||||||
|
{ pattern: /<strong>\d+<\/strong> Hooks/g, replacement: `<strong>${hooks}</strong> 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] 未实际写入');
|
||||||
Loading…
Reference in New Issue
Block a user