From d01a6c578790d6df61a68b56914effed95c326b5 Mon Sep 17 00:00:00 2001 From: arno Date: Thu, 30 Apr 2026 16:46:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD(tools):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=A4=9A=20Provider=20=E5=90=AF=E5=8A=A8=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=92=8C=E5=8A=A8=E6=80=81=E4=B8=8A=E4=B8=8B=E6=96=87=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 runcc.sh 支持 GLM/DeepSeek 等多 Provider 动态切换 - 新增 .env.example 模板和 .gitignore 保护敏感配置 - 新增 bashrc.tail.sh 提供模型快捷别名和 tmux pane 级模型隔离 - 重构 status_line.sh 支持动态上下文窗口显示和压缩状态提示 - 移除 settings.json 中硬编码的压缩参数,改由 runcc.sh 动态管理 --- .claude/settings.json | 2 - .claude/tools/.env.example | 36 +++++++ .claude/tools/.gitignore | 1 + .claude/tools/bashrc.tail.sh | 40 ++++++++ .claude/tools/runcc.sh | 177 +++++++++++++++++++++++++++++++++++ .claude/tools/status_line.sh | 31 ++++-- 6 files changed, 279 insertions(+), 8 deletions(-) create mode 100644 .claude/tools/.env.example create mode 100644 .claude/tools/.gitignore create mode 100755 .claude/tools/bashrc.tail.sh create mode 100755 .claude/tools/runcc.sh diff --git a/.claude/settings.json b/.claude/settings.json index 5aca21f..d51136b 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,7 +1,5 @@ { "env": { - "CLAUDE_CODE_AUTO_COMPACT_WINDOW": "200000", - "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "75", "CLAUDE_CODE_NO_FLICKER": "1" }, "hooks": { diff --git a/.claude/tools/.env.example b/.claude/tools/.env.example new file mode 100644 index 0000000..cf27c0c --- /dev/null +++ b/.claude/tools/.env.example @@ -0,0 +1,36 @@ +# ── Provider 选择 ──────────────────────────────────────── +# 变量命名规则: _BASE_URL, _AUTH_TOKEN, _DEFAULT_MODEL, _CONTEXT_WINDOW +# 例如 CC_PROVIDER=glm → 读取 GLM_BASE_URL, GLM_AUTH_TOKEN, GLM_DEFAULT_MODEL, GLM_CONTEXT_WINDOW +CC_PROVIDER=glm + +# ── Claude Code 路径配置 ────────────────────────────────── +# settings.json 路径(用于清理残留的旧配置) +SETTINGS_FILE=$HOME/.claude/settings.json + +# ── GLM Coding Plan (open.bigmodel.cn) ────────────────── +GLM_BASE_URL=https://open.bigmodel.cn/api/anthropic +GLM_AUTH_TOKEN={{your_glm_api_key}} +GLM_DEFAULT_MODEL=51 +GLM_CONTEXT_WINDOW=200000 +GLM_COMPACT_PCT=75 + +# ── DeepSeek (api.deepseek.com) ───────────────────────── +# 取消注释以下行以启用 DeepSeek: +# DEEPSEEK_BASE_URL=https://api.deepseek.com/anthropic +# DEEPSEEK_AUTH_TOKEN={{your_deepseek_api_key}} +# DEEPSEEK_DEFAULT_MODEL=ds +# DEEPSEEK_CONTEXT_WINDOW=1000000 +# DEEPSEEK_COMPACT_PCT=50 +# DEEPSEEK_DISABLE_COMPACT=1 +# DEEPSEEK_MAX_CONTEXT_TOKENS=1000000 + +# ── 添加新 Provider ────────────────────────────────────── +# 按以下格式添加即可,无需修改 runcc.sh: +# MYPROVIDER_BASE_URL=https://api.example.com/anthropic +# MYPROVIDER_AUTH_TOKEN=your-token +# MYPROVIDER_DEFAULT_MODEL=model-name +# MYPROVIDER_CONTEXT_WINDOW=200000 +# MYPROVIDER_COMPACT_PCT=75 +# MYPROVIDER_DISABLE_COMPACT=1 # 可选:禁用自动压缩(API 报告值 < 实际值时使用) +# MYPROVIDER_MAX_CONTEXT_TOKENS=1000000 # 可选:覆盖 Claude Code 的上下文窗口假设 +# 然后设置 CC_PROVIDER=myprovider 即可使用 diff --git a/.claude/tools/.gitignore b/.claude/tools/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.claude/tools/.gitignore @@ -0,0 +1 @@ +.env diff --git a/.claude/tools/bashrc.tail.sh b/.claude/tools/bashrc.tail.sh new file mode 100755 index 0000000..fd1abb0 --- /dev/null +++ b/.claude/tools/bashrc.tail.sh @@ -0,0 +1,40 @@ +# ============================================================================ +# DevContainer .bashrc 追加配置 +# 由 Dockerfile 通过 sed 替换占位符后追加到 ~/.bashrc +# ============================================================================ + +# 别名配置 +alias ll='ls -alhF --color=auto' +alias date='date "+%F %T %p %A %B"' +export TIME_STYLE='+%Y-%m-%d %H:%M:%S' + +export BIN_PATH="$HOME/.local/bin" + +# Claude Code 启动函数(参数透传至 runcc.sh,支持 -n/--name 走 /rename 持久化) +cc() { $BIN_PATH/runcc.sh "$@"; } + +# 模型快捷方式: cc51, cc51 -n 后端, cc -n 后端 51 ... +alias cc45='cc 45' +alias cc45a='cc 45a' +alias cc46='cc 46' +alias cc47='cc 47' +alias cc5='cc 5' +alias cc5t='cc 5t' +alias cc51='cc 51' + +# DeepSeek 快捷方式 +alias ccds='cc -p deepseek ds' +alias ccdsf='cc -p deepseek dsf' + +# tmux pane 级模型继承:从 pane option 读取模型配置(由 runcc.sh 写入) +# 覆盖 session 级环境变量,实现同 session 不同 pane 的模型隔离 +if [ -n "${TMUX:-}" ]; then + _model="$(tmux show-option -pv @anthropic_model 2>/dev/null || true)" + if [ -n "$_model" ]; then + export ANTHROPIC_DEFAULT_HAIKU_MODEL="$_model" + export ANTHROPIC_DEFAULT_SONNET_MODEL="$_model" + export ANTHROPIC_DEFAULT_OPUS_MODEL="$_model" + fi + unset _model + +fi diff --git a/.claude/tools/runcc.sh b/.claude/tools/runcc.sh new file mode 100755 index 0000000..026460e --- /dev/null +++ b/.claude/tools/runcc.sh @@ -0,0 +1,177 @@ +#!/bin/bash +# Claude Code 启动脚本 +# 支持多 Provider (通过 .env 配置,自动解析 ${PROVIDER}_BASE_URL / ${PROVIDER}_AUTH_TOKEN) +# +# 固定版本: 2.1.112 (来自 /workspace/.devcontainer/.cache/npm/anthropic-ai-claude-code-2.1.112.tgz) +# 注意: 不再执行自动更新,以保持版本稳定 + +# ── 路径配置 ────────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_FILE="${SCRIPT_DIR}/.env" + +# ── 加载 .env 文件 ────────────────────────────────────── +if [ -f "$ENV_FILE" ]; then + while IFS= read -r line || [ -n "$line" ]; do + # 跳过注释和空行 + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ -z "${line// /}" ]] && continue + # 解析 key=value + key="${line%%=*}" + value="${line#*=}" + # 去除引号 + [[ "$value" =~ ^\"(.*)\"$ ]] && value="${BASH_REMATCH[1]}" + [[ "$value" =~ ^\'(.*)\'$ ]] && value="${BASH_REMATCH[1]}" + # 仅导出合法变量名 + if [[ "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then + export "$key=$value" + fi + done < "$ENV_FILE" +fi + +# ── 参数解析 ────────────────────────────────────────────── +SESSION_NAME="" +MODEL_KEY="" +PROVIDER_OVERRIDE="" + +while [ $# -gt 0 ]; do + case "$1" in + --name|-n) + [ $# -ge 2 ] || { echo "错误: --name 需要参数" >&2; exit 1; } + SESSION_NAME="$2"; shift 2 + ;; + --provider|-p) + [ $# -ge 2 ] || { echo "错误: --provider 需要参数" >&2; exit 1; } + PROVIDER_OVERRIDE="$2"; shift 2 + ;; + -h|--help) + echo "用法: runcc.sh [选项] [模型]" + echo "" + echo "选项:" + echo " --name, -n <名称> Claude Code 会话显示名称" + echo " --provider, -p <名称> API 提供商 (由 .env 配置,如 glm / deepseek)" + echo "" + echo "GLM 模型: 45|45a|46|47|5|5t|51" + echo "DeepSeek 模型: ds|dsf" + echo "其他: 直接传入模型名称" + echo "" + echo "Provider 变量规则: CC_PROVIDER=_BASE_URL + _AUTH_TOKEN" + echo " 添加新 Provider 只需在 .env 中按此格式添加变量即可" + echo "" + echo "示例:" + echo " runcc.sh # 默认 Provider + 默认模型" + echo " runcc.sh 47 # 默认 Provider + glm-4.7" + echo " runcc.sh --provider deepseek # DeepSeek + 默认模型" + echo " runcc.sh -p deepseek dsf # DeepSeek + deepseek-v4-flash" + exit 0 + ;; + *) + MODEL_KEY="$1"; shift + ;; + esac +done + +# ── Provider 动态解析 ───────────────────────────────────── +# CC_PROVIDER=glm → GLM_BASE_URL, GLM_AUTH_TOKEN, GLM_DEFAULT_MODEL +# CC_PROVIDER=deepseek → DEEPSEEK_BASE_URL, DEEPSEEK_AUTH_TOKEN, DEEPSEEK_DEFAULT_MODEL +# CC_PROVIDER=xxx → XXX_BASE_URL, XXX_AUTH_TOKEN, XXX_DEFAULT_MODEL, XXX_CONTEXT_WINDOW +CC_PROVIDER="${PROVIDER_OVERRIDE:-${CC_PROVIDER:-glm}}" +upper_provider="$(echo "$CC_PROVIDER" | tr '[:lower:]' '[:upper:]')" + +base_url_var="${upper_provider}_BASE_URL" +token_var="${upper_provider}_AUTH_TOKEN" +default_model_var="${upper_provider}_DEFAULT_MODEL" +context_window_var="${upper_provider}_CONTEXT_WINDOW" +compact_pct_var="${upper_provider}_COMPACT_PCT" +disable_compact_var="${upper_provider}_DISABLE_COMPACT" +max_ctx_var="${upper_provider}_MAX_CONTEXT_TOKENS" + +ANTHROPIC_BASE_URL="${!base_url_var}" +ANTHROPIC_AUTH_TOKEN="${!token_var}" +DEFAULT_MODEL="${!default_model_var:-}" +CONTEXT_WINDOW="${!context_window_var:-200000}" +COMPACT_PCT="${!compact_pct_var:-75}" +DISABLE_COMPACT="${!disable_compact_var:-}" +MAX_CONTEXT_TOKENS="${!max_ctx_var:-}" + +if [ -z "$ANTHROPIC_BASE_URL" ] || [ -z "$ANTHROPIC_AUTH_TOKEN" ]; then + echo "错误: Provider '$CC_PROVIDER' 未配置 BASE_URL 或 AUTH_TOKEN" >&2 + echo "请在 ${ENV_FILE} 中添加: ${upper_provider}_BASE_URL, ${upper_provider}_AUTH_TOKEN" >&2 + exit 1 +fi + +export ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN +export CC_CONTEXT_WINDOW="$CONTEXT_WINDOW" + +# DISABLE_COMPACT 模式: 禁用自动压缩,覆盖上下文窗口(适用于 API 报告值 < 实际值的模型) +if [ "$DISABLE_COMPACT" = "1" ]; then + export DISABLE_COMPACT=1 + export CLAUDE_CODE_MAX_CONTEXT_TOKENS="${MAX_CONTEXT_TOKENS:-$CONTEXT_WINDOW}" +else + export CLAUDE_AUTOCOMPACT_PCT_OVERRIDE="$COMPACT_PCT" +fi + +# ── 更新 settings.json ────────────────────────────────── +# 删除凭据、模型和窗口变量(由环境变量接管) +# SETTINGS_FILE 路径由 .env 配置,默认 ~/.claude/settings.json +SETTINGS_FILE="${SETTINGS_FILE:-$HOME/.claude/settings.json}" +if command -v jq &>/dev/null && [ -f "$SETTINGS_FILE" ]; then + jq 'del(.env.ANTHROPIC_BASE_URL, .env.ANTHROPIC_AUTH_TOKEN, .env.CLAUDE_CODE_AUTO_COMPACT_WINDOW, .env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE, .env.DISABLE_COMPACT, .env.CLAUDE_CODE_MAX_CONTEXT_TOKENS, .env.ANTHROPIC_DEFAULT_OPUS_MODEL, .env.ANTHROPIC_DEFAULT_HAIKU_MODEL, .env.ANTHROPIC_DEFAULT_SONNET_MODEL)' \ + "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" \ + && mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE" +fi + +# ── 模型解析 ────────────────────────────────────────────── +MODEL_KEY="${MODEL_KEY:-$DEFAULT_MODEL}" + +case "$MODEL_KEY" in + # GLM 系列 + 45) MODEL="glm-4.5" ;; + 45a) MODEL="glm-4.5-air" ;; + 46) MODEL="glm-4.6" ;; + 47) MODEL="glm-4.7" ;; + 5) MODEL="glm-5" ;; + 5t) MODEL="glm-5-turbo" ;; + 51) MODEL="glm-5.1" ;; + # DeepSeek 系列 + ds) MODEL="deepseek-v4-pro" ;; + dsf) MODEL="deepseek-v4-flash" ;; + # 其他: 直接作为模型名称 + *) MODEL="$MODEL_KEY" ;; +esac + +# ── 模型环境变量(三个层级统一为同一模型)──────────────── +export ANTHROPIC_DEFAULT_HAIKU_MODEL="$MODEL" +export ANTHROPIC_DEFAULT_SONNET_MODEL="$MODEL" +export ANTHROPIC_DEFAULT_OPUS_MODEL="$MODEL" + +# ── tmux 模型传播(确保子 pane 能获取正确模型)───────── +if [ -n "${TMUX:-}" ]; then + SESSION_ID="$(tmux display-message -p '#{session_id}')" + + tmux setenv -t "$SESSION_ID" ANTHROPIC_DEFAULT_OPUS_MODEL "$MODEL" + tmux setenv -t "$SESSION_ID" ANTHROPIC_DEFAULT_HAIKU_MODEL "$MODEL" + tmux setenv -t "$SESSION_ID" ANTHROPIC_DEFAULT_SONNET_MODEL "$MODEL" + tmux setenv -t "$SESSION_ID" CC_CONTEXT_WINDOW "$CONTEXT_WINDOW" + + if [ "$DISABLE_COMPACT" = "1" ]; then + tmux setenv -t "$SESSION_ID" DISABLE_COMPACT 1 + tmux setenv -t "$SESSION_ID" CLAUDE_CODE_MAX_CONTEXT_TOKENS "${MAX_CONTEXT_TOKENS:-$CONTEXT_WINDOW}" + tmux setenv -u -t "$SESSION_ID" CLAUDE_AUTOCOMPACT_PCT_OVERRIDE 2>/dev/null || true + else + tmux setenv -t "$SESSION_ID" CLAUDE_AUTOCOMPACT_PCT_OVERRIDE "$COMPACT_PCT" + tmux setenv -u -t "$SESSION_ID" DISABLE_COMPACT 2>/dev/null || true + tmux setenv -u -t "$SESSION_ID" CLAUDE_CODE_MAX_CONTEXT_TOKENS 2>/dev/null || true + fi + + tmux set-option -p @anthropic_model "$MODEL" +fi + +# ── 启动 Claude Code ──────────────────────────────────── +CC_ARGS=(--ide --dangerously-skip-permissions --allow-dangerously-skip-permissions --effort max) + +if [ -n "$SESSION_NAME" ]; then + INITIAL_PROMPT="/rename $SESSION_NAME" +fi + +echo "启动 Claude Code (provider: $CC_PROVIDER, 模型: $MODEL${SESSION_NAME:+, 会话: $SESSION_NAME})" +claude "${CC_ARGS[@]}" ${INITIAL_PROMPT:+"$INITIAL_PROMPT"} diff --git a/.claude/tools/status_line.sh b/.claude/tools/status_line.sh index aa07725..e85215a 100755 --- a/.claude/tools/status_line.sh +++ b/.claude/tools/status_line.sh @@ -3,15 +3,28 @@ input=$(cat) # Extract fields using jq -MODEL=$(echo "$input" | jq -r '.model.display_name' | sed 's/\[1m\]/[200k]/') +MODEL=$(echo "$input" | jq -r '.model.display_name') DIR=$(echo "$input" | jq -r '.workspace.current_dir') V=$(echo "$input" | jq -r '.version') +# 上下文窗口大小(由 runcc.sh 根据 Provider 设置并写入 settings.json, +# 默认 200000。GLM=200k, DeepSeek=1M) +CC_CONTEXT_WINDOW="${CC_CONTEXT_WINDOW:-200000}" + +# 格式化上下文窗口: 200000→200k, 1000000→1M +if [ "$CC_CONTEXT_WINDOW" -ge 1000000 ]; then + CW_DISPLAY="$((CC_CONTEXT_WINDOW / 1000000))M" +else + CW_DISPLAY="$((CC_CONTEXT_WINDOW / 1000))k" +fi + +# 模型名后追加上下文窗口大小(避免重复追加) +if [[ "$MODEL" != *"$CW_DISPLAY"* ]]; then + MODEL="${MODEL} [${CW_DISPLAY}]" +fi + # 计算上下文使用占比 -# JSON 中 used_percentage 是相对模型实际窗口(如 GLM 的 1M)计算的, -# 但 /context 命令显示的是相对 auto-compact 窗口(200k)的占比。 -# 因此需要用实际 token 数 / 200000 来获得正确的百分比。 -AUTO_COMPACT_WINDOW=200000 +AUTO_COMPACT_WINDOW="$CC_CONTEXT_WINDOW" PCT=$(echo "$input" | jq -r --argjson w "$AUTO_COMPACT_WINDOW" ' (( (.context_window.current_usage.input_tokens // 0) + @@ -34,4 +47,10 @@ elif git rev-parse --git-dir >/dev/null 2>&1; then REMOTE="🌐 $(git remote get-url origin 2>/dev/null)" fi -echo "版本:v$V | 模型:$MODEL | 上下文:${PCT}%" \ No newline at end of file +# DISABLE_COMPACT 提醒:无自动压缩,需手动 /compact 或 /clear +COMPACT_HINT="" +if [ "${DISABLE_COMPACT:-0}" = "1" ]; then + COMPACT_HINT=" | ⚠无自动压缩" +fi + +echo "版本:v$V | 模型:$MODEL | 上下文:${PCT}%${COMPACT_HINT}"