配置: 初始化 ISOS Agent Teams 软件研发模板
CI / lint (push) Successful in 6s

This commit is contained in:
2026-04-19 21:47:08 +08:00
parent 3ab6fe6504
commit 34346be862
202 changed files with 23544 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
# ============================================================================
# 开发容器环境变量配置
# ============================================================================
#
# 使用说明:
# 1. 复制此文件: cp .env.example .env
# 2. 根据实际情况修改下面的配置项
# 3. 运行启动脚本: bash start.sh
#
# ============================================================================
# 容器用户 UID/GID
# 用途: 容器内用户的 UID 和 GID
# 建议: 设置为与宿主机当前用户一致的 UID/GID,避免文件权限问题
# 获取方式: 在宿主机运行 'id -u' 和 'id -g'
CONTAINER_USER_UID=1000
CONTAINER_USER_GID=1000
# 项目名称
# 用途: 容器名称、主机名等标识
PROJECT_NAME=team
# 持久化卷路径配置
# 用途: 指定 SSH、Claude Code、jj 等配置的持久化路径
CLAUDE_PATH=.volumes/claude
SSH_PATH=.volumes/ssh
JJ_CONFIG_PATH=.volumes/jj
# Docker 配置
# 用途: 控制容器内是否启用 Docker 访问
# 设置为 false 时,不会配置 Docker 组权限,也不会挂载 Docker socket
DOCKER_ENABLED=false
# Docker Socket 路径
# 用途: 指定 Docker socket 文件的挂载源路径
# 当 DOCKER_ENABLED=true 时,设置为 /var/run/docker.sock 以启用 Docker socket 挂载
# 当 DOCKER_ENABLED=false 时,设置为 /dev/null 以禁用 Docker socket 挂载
DOCKER_SOCK=/var/run/docker.sock
# Docker 组 GID
# 用途: 容器内 docker 组的 GID,需与宿主机 docker 组 GID 一致
# 获取方式: 在宿主机运行 'getent group docker | cut -d: -f3'
# 仅在 DOCKER_ENABLED=true 时生效
DOCKER_GID=984
# X11 宿主机显示配置
# 用途: 控制容器内 GUI 应用(如 Playwright headed 模式)是否在宿主机显示
# 设置为 true 时,需要宿主机运行 X Server 并允许容器连接
# 设置为 false 时(默认),容器内使用 Xvfb 虚拟显示
# 前提: 仅 Linux 宿主机支持,macOS/Windows 需使用 VNC 方案
DISPLAY_ON_HOST=false
# 宿主机 X11 Display(通常无需修改,由宿主机自动获取)
# 获取方式: 在宿主机运行 'echo $DISPLAY'
HOST_DISPLAY=:0
# Git 用户配置
# 用途: 容器内的 Git 全局配置
GIT_USER_NAME=arno
GIT_USER_EMAIL=arno_jin@bis.com.cn
# API 密钥配置
# 用途: 智谱 GLM Coding Plan API 密钥,用于 Claude Code 和 Open Code 认证
# 获取方式: 在 https://open.bigmodel.cn/ 注册并获取 API Key
# 注意: 此密钥会注入到 ~/.claude.jsonMCP 服务器)和 ~/.claude/settings.jsonAUTH_TOKEN
API_KEY=
# 容器资源限制
# 用途: 控制容器可使用的 CPU 核心数和内存上限
# 建议: 根据宿主机资源情况调整,开发环境推荐 4 核 8G 起步
CONTAINER_CPUS=6
CONTAINER_MEMORY=16G
# ============================================================================
# 工具版本配置(download-resources.sh 使用)
# ============================================================================
# UV 包管理器版本
UV_VERSION=0.10.4
# NVM 版本
NVM_VERSION=v0.40.3
# Node.js 版本
NODE_VERSION=22.22.0
# npm 版本
NPM_VERSION=11.12.1
# Google Chrome 版本
CHROME_VERSION=146.0.7680.177-1
# Python 大版本号(用于 uv python-build-standalone
PYTHON_VERSION=3.12
# Electron 版本
ELECTRON_VERSION=v32.3.3
# jj (Jujutsu) 版本控制工具版本
JJ_VERSION=v0.40.0
+229
View File
@@ -0,0 +1,229 @@
# 使用本地缓存资源的 Dockerfile
# 构建前请先运行 ./download-resources.sh 下载所需资源
# 注意:默认情况下 .env 文件对 Dockerfile 是无效的
# ============================================================================
# 基础镜像: 全局环境变量和标签
# ============================================================================
FROM mcr.microsoft.com/devcontainers/base:noble AS base_0_5
ARG CONTAINER_USER_UID=1000
ARG CONTAINER_USER_GID=1000
ARG PROJECT_NAME=isos
ARG DOCKER_GID=984
ENV TZ="Asia/Shanghai" \
CONTAINER_USER_UID="$CONTAINER_USER_UID" \
CONTAINER_USER_GID="$CONTAINER_USER_GID" \
PROJECT_NAME="$PROJECT_NAME" \
DOCKER_GID="$DOCKER_GID" \
PROJECT_ROOT="/workspace" \
DEBIAN_FRONTEND=noninteractive
LABEL maintainer="Arno Jin <arno@arnojin.com>" \
base.image="mcr.microsoft.com/devcontainers/base:noble" \
ubuntu.version="24.04" \
uv.version="installed" \
spec-kit.version="installed" \
container.name="$PROJECT_NAME" \
container.hostname="$PROJECT_NAME"
# ============================================================================
# 阶段 1/4: 系统级安装 + Docker CLI(需要 root 权限)
# ============================================================================
# 注意: mcr.microsoft.com/devcontainers/base:noble 已预装:
# 核心: git, curl, wget, sudo, jq, unzip, zip, gnupg, xz-utils, patch
# 编辑: vim-tiny, vim-common, nano
# 构建: build-essential (gcc, g++, make), libc6-dev, libssl-dev, zlib1g-dev
# 网络: iproute2, net-tools, openssh-client
# 系统: locales, tzdata, ca-certificates, htop, strace, lsof, ncdu, tree, rsync
# Shell: bash, zsh (+ oh-my-zsh)
# 用户: vscode (UID/GID 1000, sudo NOPASSWD, shell=/bin/bash)
# Root: shell=/bin/bash
# 以下工具/库需要额外安装:
FROM base_0_5 AS system_and_docker_1_4
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
set -eu && \
# 配置清华镜像源
sed -i 's|http://archive.ubuntu.com|https://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/ubuntu.sources \
&& sed -i 's|http://security.ubuntu.com|https://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/ubuntu.sources \
# 移除失效的 Yarn APT 源
&& rm -f /etc/apt/sources.list.d/yarn.list \
/usr/share/keyrings/yarn-archive-keyring.gpg \
/etc/apt/sources.list.d/yarn.list.bak 2>/dev/null || true \
# 安装系统包
&& apt-get update \
&& apt-get install -y --no-install-recommends \
bats tmux ssh ipset iptables \
iputils-ping dnsutils telnet fzf vim xvfb xauth x11-utils \
fonts-inter fonts-noto-cjk fonts-jetbrains-mono fonts-liberation fontconfig fonts-noto-color-emoji \
# Playwright Chromium 核心依赖
libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 libatspi2.0-0t64 \
libdrm2 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libxext6 \
libgbm1 libxkbcommon0 libpango-1.0-0 libcairo2 libcairo-gobject2 \
libasound2t64 libcups2t64 libgtk-3-0t64 libvulkan1 xdg-utils \
# Playwright Chromium X11/Wayland 依赖
libdbus-1-3 libx11-6 libxcb1 libxcursor1 libxi6 libxrender1 \
libx11-xcb1 libxcb-shm0 libglib2.0-0t64 \
# Playwright Chromium 渲染/媒体依赖
libfontconfig1 libfreetype6 libgdk-pixbuf-2.0-0 libpangocairo-1.0-0 \
libavcodec60 libopus0 libvpx9 libwebp7 libxslt1.1 libharfbuzz0b \
libjpeg-turbo8 libpng16-16t64 libavif16 liblcms2-2 libevent-2.1-7t64 \
# GPU 支持
libgl1 libegl1 \
# --- Docker CLIDooD 模式,仅 CLI,不含 daemon,使用清华镜像源)---
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& chmod a+r /etc/apt/keyrings/docker.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
docker-ce-cli \
docker-buildx-plugin \
docker-compose-plugin \
# --- 系统配置 ---
&& locale-gen zh_CN.UTF-8 \
&& update-locale LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 \
&& echo "Asia/Shanghai" > /etc/timezone \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& fc-cache -fv \
&& echo 'fs.inotify.max_user_watches=524288' >> /etc/sysctl.d/10-sysctl.conf \
&& rm -rf /var/lib/apt/lists/* \
/tmp/* \
/var/tmp/* \
/var/cache/apt/archives/*.deb
# ============================================================================
# 阶段 2/4: 安装 Chrome + 配置用户和目录权限(需要 root 权限)
# ============================================================================
FROM system_and_docker_1_4 AS chrome_2_4
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=bind,source=.devcontainer/.cache/chrome,target=/tmp/chrome-cache \
set -eu && \
apt-get update \
&& dpkg -i /tmp/chrome-cache/google-chrome-stable_*.deb || apt-get install -y -f \
&& google-chrome --version \
&& rm -rf /var/lib/apt/lists/* \
&& if [ "$CONTAINER_USER_UID" != "1000" ] || [ "$CONTAINER_USER_GID" != "1000" ]; then \
groupmod -g "$CONTAINER_USER_GID" vscode && \
usermod -u "$CONTAINER_USER_UID" -d "/home/vscode" vscode && \
chown -R "$CONTAINER_USER_UID:$CONTAINER_USER_GID" /home/vscode; \
fi \
&& groupmod -g ${DOCKER_GID} docker 2>/dev/null || groupadd -g ${DOCKER_GID} docker 2>/dev/null || true \
&& usermod -aG docker vscode \
&& mkdir -p $PROJECT_ROOT \
&& chown -R vscode:vscode $PROJECT_ROOT
# ============================================================================
# 阶段 3/4: 安装开发工具(普通用户)
# ============================================================================
FROM chrome_2_4 AS dev_tools_3_4
ENV BIN_PATH="$PROJECT_ROOT/.devcontainer/.volumes/bin" \
NVM_DIR="/home/vscode/.nvm" \
NODE_VERSION="22.22.0" \
UV_PYTHON_DIR="/home/vscode/.local/share/uv" \
UV_BIN="/home/vscode/.local/bin" \
PATH="/home/vscode/.local/bin:$PATH" \
UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
UV_LINK_MODE=copy \
PYTHONUNBUFFERED=1 \
COREPACK_ENABLE_DOWNLOAD_PROMPT=false \
LC_ALL=zh_CN.UTF-8 \
LC_TIME=zh_CN.UTF-8 \
LANG=zh_CN.UTF-8 \
DEVCONTAINER=true \
EDITOR=vim \
VISUAL=vim \
SHELL=/bin/bash \
DISPLAY=""
WORKDIR $PROJECT_ROOT
USER vscode
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
# 使用 --mount=type=bind 挂载缓存资源(避免 COPY 层残留临时文件)
RUN --mount=type=bind,source=.devcontainer/.cache/uv,target=/tmp/uv-cache \
--mount=type=bind,source=.devcontainer/.cache/node,target=/tmp/node-cache \
--mount=type=bind,source=.devcontainer/.cache/npm,target=/tmp/npm-cache \
--mount=type=bind,source=.devcontainer/.cache/vscode,target=/tmp/vscode-cache \
--mount=type=bind,source=.devcontainer/.cache/nvm,target=/tmp/nvm-cache \
--mount=type=bind,source=.devcontainer/.cache/spec-kit,target=/tmp/spec-kit-cache \
--mount=type=bind,source=.devcontainer/.cache/jj,target=/tmp/jj-cache \
mkdir -p "$UV_BIN" "$UV_PYTHON_DIR" "$NVM_DIR" \
&& tar -xzf /tmp/uv-cache/uv-x86_64-unknown-linux-gnu.tar.gz -C "$UV_BIN" --strip-components=1 \
&& tar -xzf /tmp/uv-cache/cpython-*-x86_64-unknown-linux-gnu-install_only.tar.gz -C "$UV_PYTHON_DIR" \
&& ln -sf "$UV_PYTHON_DIR/python/bin/python3.12" "$UV_BIN/python3" \
&& ln -sf "$UV_PYTHON_DIR/python/bin/python3.12" "$UV_BIN/python" \
&& "$UV_BIN/uv" tool install --python "$UV_PYTHON_DIR/python/bin/python3.12" /tmp/spec-kit-cache \
&& ln -sf "/home/vscode/.local/share/uv/tools/specify-cli/bin/specify" "$UV_BIN/specify" \
&& cp -r /tmp/nvm-cache/. "$NVM_DIR/" \
&& [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" \
&& mkdir -p "$NVM_DIR/versions/node" \
&& tar -xJf /tmp/node-cache/node-v*.tar.xz -C "$NVM_DIR/versions/node" \
&& NODE_DIR=$(ls -d "$NVM_DIR/versions/node"/node-v* | head -n1) \
&& mv "$NODE_DIR" "$NVM_DIR/versions/node/v${NODE_VERSION}" \
&& nvm use v${NODE_VERSION} \
&& nvm alias default v${NODE_VERSION} \
&& export PATH="$NVM_DIR/versions/node/v${NODE_VERSION}/bin:$PATH" \
&& export npm_config_registry=https://registry.npmmirror.com \
&& find /tmp/npm-cache/ -maxdepth 1 -name "npm-*.tgz" -exec npm install -g {} + \
&& find /tmp/npm-cache/ -maxdepth 1 -name "*.tgz" ! -name "npm-*.tgz" -exec npm install -g {} + \
# 安装 VSCode 扩展
&& mkdir -p "/home/vscode/.vscode-server/extensions" \
&& for ext in /tmp/vscode-cache/*.vsix; do \
[ -f "$ext" ] || continue; \
ext_name=$(basename "$ext" .vsix); \
temp_dir=$(mktemp -d); \
unzip -q "$ext" -d "$temp_dir" 2>/dev/null || true; \
[ -d "$temp_dir/extension" ] || { rm -rf "$temp_dir"; continue; }; \
version=$(jq -r '.version' "$temp_dir/extension/package.json" 2>/dev/null || echo "0.0.0"); \
ext_dir="/home/vscode/.vscode-server/extensions/${ext_name}-${version}"; \
mkdir -p "$ext_dir"; \
cp -r "$temp_dir/extension"/* "$ext_dir/"; \
rm -rf "$temp_dir"; \
done \
# 安装 Jujutsu (jj) 版本控制工具
&& jj_tar=$(ls /tmp/jj-cache/jj-*.tar.gz 2>/dev/null | head -n1) \
&& if [ -n "$jj_tar" ]; then \
mkdir -p /tmp/jj-extract \
&& tar -xzf "$jj_tar" -C /tmp/jj-extract \
&& cp /tmp/jj-extract/jj "$UV_BIN/jj" \
&& chmod +x "$UV_BIN/jj" \
&& jj --version; \
fi
# 将 Node.js 加入镜像级 PATH,确保非交互式进程也能找到 node/npm
ENV PATH="/home/vscode/.nvm/versions/node/v${NODE_VERSION}/bin:${PATH}"
# ============================================================================
# 阶段 4/4: 配置用户 shell 环境(.bashrc
# ============================================================================
FROM dev_tools_3_4 AS shell_env_4_4
SHELL ["/bin/bash", "-e", "-u", "-o", "pipefail", "-c"]
# 修复 /usr/local/bin/code 包装脚本
USER root
COPY .devcontainer/scripts/code-wrapper /usr/local/bin/code
RUN chmod +x /usr/local/bin/code
# 使用模板文件替代逐行 echo,通过 sed 替换占位符
COPY .devcontainer/templates/bashrc.tail.sh /tmp/bashrc.tail.sh
RUN sed "s|{{NODE_PATH}}|/home/vscode/.nvm/versions/node/v${NODE_VERSION}/bin|g; \
s|{{BIN_PATH}}|${BIN_PATH}|g" \
/tmp/bashrc.tail.sh >> /home/vscode/.bashrc \
&& rm -f /tmp/bashrc.tail.sh \
&& chown vscode:vscode /home/vscode/.bashrc
USER vscode
RUN mkdir -p /home/vscode/.ssh /home/vscode/.claude \
&& chmod 700 /home/vscode/.ssh
CMD ["sleep", "infinity"]
+67
View File
@@ -0,0 +1,67 @@
{
"name": "team",
"service": "app",
"remoteUser": "vscode",
"updateRemoteUserUID": false,
"workspaceFolder": "/workspace",
"dockerComposeFile": [
"docker-compose.yml",
"docker-compose.display.yml"
],
"shutdownAction": "none",
"forwardPorts": [],
"customizations": {
"vscode": {
"extensions": [
"anthropic.claude-code",
"bierner.markdown-mermaid",
"charliermarsh.ruff",
"dbaeumer.vscode-eslint",
"github.copilot",
"github.copilot-chat",
"humao.rest-client",
"james-yu.latex-workshop",
"marp-team.marp-vscode",
"ms-ceintl.vscode-language-pack-zh-hans",
"ms-playwright.playwright",
"ms-python.debugpy",
"ms-python.python",
"ms-python.vscode-pylance",
"ms-vscode-remote.remote-containers",
"ms-vscode-remote.remote-ssh",
"mutantdino.resourcemonitor",
"redhat.vscode-yaml",
"shd101wyy.markdown-preview-enhanced",
"tamasfe.even-better-toml",
"tomoki1207.pdf",
"yzane.markdown-pdf",
"yzhang.markdown-all-in-one"
],
"settings": {
"extensions.autoUpdate": "false",
"extensions.autoCheckUpdates": false,
"remote.autoForwardPorts": false,
"remote.autoForwardPortsSource": "output",
"resmon.show.battery": false,
"resmon.show.cpufreq": false,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"terminal.integrated.defaultProfile.linux": "bash",
"terminal.integrated.profiles.linux": {
"bash": {
"path": "bash",
"icon": "terminal-bash"
},
"zsh": {
"path": "zsh"
}
},
"python.terminal.activateEnvironment": true,
"markdown-preview-enhanced.chromePath": "/usr/bin/google-chrome-stable"
}
}
},
"postCreateCommand": "chmod +x ./.devcontainer/post-create.sh && ./.devcontainer/post-create.sh"
}
+21
View File
@@ -0,0 +1,21 @@
# X11 宿主机显示覆盖配置
#
# 此文件始终被 devcontainer.json 加载,通过 DISPLAY_ON_HOST 变量控制行为:
# DISPLAY_ON_HOST=true → 挂载 X11 socket,覆盖 DISPLAY 环境变量
# DISPLAY_ON_HOST=false → 挂载路径为空值(被忽略),不影响主配置
#
# 宿主机准备(Linux:
# 1. 允许本地 X 连接: xhost +local:
# 2. 确认 display: echo $DISPLAY
# 3. 在 .env 中设置 DISPLAY_ON_HOST=true 和 HOST_DISPLAY=$DISPLAY
services:
app:
environment:
DISPLAY_ON_HOST: ${DISPLAY_ON_HOST:-false}
# 仅当 DISPLAY_ON_HOST=true 时,post-create.sh 使用此值替代 Xvfb
HOST_DISPLAY: ${HOST_DISPLAY:-:0}
volumes:
# X11 socket 挂载:始终挂载,容器内脚本按需检测
# 如果宿主机无 X11(如 macOS/Windows),此目录为空,不影响运行
- /tmp/.X11-unix:/tmp/.host-x11
+81
View File
@@ -0,0 +1,81 @@
# Docker Compose 配置文件
#
# 启动前准备:
# 1. 复制环境变量配置: cp .env.example .env
# 2. 根据需要修改 .env 文件
# 3. (可选)预下载构建资源: bash download-resources.sh
#
# 启动方式:
# 方法 1(推荐): bash start.sh
# 方法 2: docker compose up(需要确保 .env 文件存在)
services:
app:
env_file:
- .env
build:
context: ..
dockerfile: .devcontainer/Dockerfile
args:
CONTAINER_USER_UID: ${CONTAINER_USER_UID:-1000}
CONTAINER_USER_GID: ${CONTAINER_USER_GID:-1000}
PROJECT_NAME: ${PROJECT_NAME:-isos}
DOCKER_GID: ${DOCKER_GID:-984}
image: vscode:${PROJECT_NAME:-isos}
container_name: ${PROJECT_NAME:-isos}
hostname: ${PROJECT_NAME:-isos}
extra_hosts:
- "${PROJECT_NAME:-isos}:127.0.0.1"
- "localhost:127.0.0.1"
# 特权模式(可选,部分场景需要)
privileged: true
# 资源限制配置
deploy:
resources:
limits:
cpus: '${CONTAINER_CPUS:-4}'
memory: ${CONTAINER_MEMORY:-8G}
# 配置 GPU 访问:all 表示所有 GPU,也可指定具体 GPU,如 "device=0,1"
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
environment:
NODE_OPTIONS: --max-old-space-size=4096
DOCKER_ENABLED: ${DOCKER_ENABLED:-true}
DOCKER_GID: ${DOCKER_GID:-984}
# 让容器可见所有 GPU
# NVIDIA_VISIBLE_DEVICES: all
# 启用计算和工具类能力
# NVIDIA_DRIVER_CAPABILITIES: compute,utility
volumes:
- ../:/workspace:delegated
- ${DOCKER_SOCK:-/var/run/docker.sock}:/var/run/docker.sock
- ${SSH_PATH:-.volumes/ssh}:/home/vscode/.ssh
- ${CLAUDE_PATH:-.volumes/claude}:/home/vscode/.claude
- ${JJ_CONFIG_PATH:-.volumes/jj}:/home/vscode/.config/jj
working_dir: /workspace
network_mode: host
ulimits:
memlock:
soft: -1
hard: -1
stack:
soft: 67108864
hard: 67108864
# 配置 IPC 模式
ipc: host
# dns:
# - 119.29.29.29
# - 223.5.5.5
# - 8.8.8.8
# - 1.1.1.1
# - 202.96.128.143
# - 202.96.128.68
# - 202.96.134.133
# - 202.96.128.86
# - 202.96.128.166
command: sleep infinity
+1134
View File
File diff suppressed because it is too large Load Diff
+539
View File
@@ -0,0 +1,539 @@
#!/bin/bash
# ==============================================================================
# DevContainer 公共函数库
# ==============================================================================
# 禁止直接执行,必须被 source
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
echo "错误: 此文件应该被 source,不能直接执行"
exit 1
fi
# ============================================================================
# 颜色代码定义
# ============================================================================
readonly COLOR_RED='\033[0;31m'
readonly COLOR_GREEN='\033[0;32m'
readonly COLOR_YELLOW='\033[0;33m'
readonly COLOR_BLUE='\033[0;34m'
readonly COLOR_PURPLE='\033[0;35m'
readonly COLOR_CYAN='\033[0;36m'
readonly COLOR_GRAY='\033[0;90m'
readonly COLOR_BOLD='\033[1m'
readonly COLOR_RESET='\033[0m'
# ============================================================================
# 状态符号函数
# ============================================================================
status_ok() {
echo -e "${COLOR_GREEN}${COLOR_RESET}"
}
status_warn() {
echo -e "${COLOR_YELLOW}⚠️${COLOR_RESET}"
}
status_miss() {
echo -e "${COLOR_RED}${COLOR_RESET}"
}
status_info() {
echo -e "${COLOR_BLUE}${COLOR_RESET}"
}
status_fail() {
echo -e "${COLOR_RED}🔴${COLOR_RESET}"
}
# ============================================================================
# 带颜色的文本函数
# ============================================================================
text_green() {
echo -e "${COLOR_GREEN}${1}${COLOR_RESET}"
}
text_yellow() {
echo -e "${COLOR_YELLOW}${1}${COLOR_RESET}"
}
text_red() {
echo -e "${COLOR_RED}${1}${COLOR_RESET}"
}
text_blue() {
echo -e "${COLOR_BLUE}${1}${COLOR_RESET}"
}
text_cyan() {
echo -e "${COLOR_CYAN}${1}${COLOR_RESET}"
}
text_gray() {
echo -e "${COLOR_GRAY}${1}${COLOR_RESET}"
}
text_bold() {
echo -e "${COLOR_BOLD}${1}${COLOR_RESET}"
}
text_purple() {
echo -e "${COLOR_PURPLE}${1}${COLOR_RESET}"
}
text_reset() {
echo -e "${COLOR_RESET}"
}
# ============================================================================
# 输出格式函数
# ============================================================================
print_header() {
local title="$1"
echo ""
echo "$(text_bold '═══════════════════════════════════════════════════════════')"
echo "$(text_bold "${title}")"
echo "$(text_bold '═══════════════════════════════════════════════════════════')"
echo ""
}
print_section() {
local title="$1"
echo ""
echo "$(text_bold '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')"
echo "$(text_bold "${title}")"
echo "$(text_bold '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')"
echo ""
}
print_step() {
local step="$1"
local desc="$2"
echo "[${step}] ${desc}"
}
print_status() {
local status="$1"
local message="$2"
printf " %s %s\n" "${status}" "${message}"
}
# ============================================================================
# 辅助函数
# ============================================================================
# 追加配置到文件(如果不存在标记)
append_config() {
local file="$1"
local marker="$2"
local content="$3"
grep -q "$marker" "$file" 2>/dev/null || echo "$content" >> "$file"
}
# 检查工具是否安装
check_tool() {
local name="$1"
local cmd="$2"
local version_cmd="${3:-$cmd --version}"
if command -v "$cmd" &>/dev/null; then
echo " $(status_ok) ${name}: $($version_cmd 2>/dev/null | head -n1)"
else
echo " $(status_miss) ${name}"
fi
}
# 检查配置文件
check_config() {
local file="$1"
local pattern="$2"
local desc="$3"
if [ -f "$file" ] && grep -q "$pattern" "$file" 2>/dev/null; then
echo " $(status_ok) ${desc}"
else
local status="配置异常"
[ ! -f "$file" ] && status="文件不存在"
echo " $(status_warn) ${desc} ($(text_red "$status"))"
fi
}
# 测试 URL 可用性
test_url() {
local name="$1"
local url="$2"
local status="$(status_fail)"
wget -q --spider --connect-timeout=3 --timeout=5 "$url" 2>/dev/null && status="$(status_ok)"
echo " ${name}: ${status}"
}
# 计算字符串显示宽度(中文字符算2个宽度)
calc_width() {
local str=$1
local width=0
local char
while read -r -n1 char; do
local code
code=$(printf '%d' "'${char}" 2>/dev/null || echo 0)
((width += (code > 127 ? 2 : 1)))
done <<< "$str"
echo "$width"
}
# ============================================================================
# 文件验证函数
# ============================================================================
# 检查文件是否存在且非空
file_exists() {
[ -f "$1" ] && [ -s "$1" ]
}
# 检查通配符文件是否存在
glob_exists() {
local file
file=$(ls $1 2>/dev/null | head -n1)
[ -n "${file}" ] && [ -f "${file}" ] && [ -s "${file}" ]
}
# 检查目录是否有效(非空)
dir_valid() {
[ -d "$1" ] && [ "$(find "$1" -maxdepth 1 -mindepth 1 -not -name ".git" 2>/dev/null | wc -l)" -gt 0 ]
}
# ============================================================================
# 下载函数(带重试和校验)
# ============================================================================
# 计算 SHA256 校验和
compute_sha256() {
local file=$1
if [ -f "${file}" ]; then
sha256sum "${file}" 2>/dev/null | cut -d' ' -f1
else
echo ""
fi
}
# 从 GitHub releases 获取预期校验和
get_github_release_checksum() {
local repo=$1
local version=$2
local filename=$3
local checksums_url="https://github.com/${repo}/releases/download/${version}/checksums.txt"
local temp_checksums
temp_checksums=$(mktemp)
if wget -q --timeout=30 -O "${temp_checksums}" "${checksums_url}" 2>/dev/null; then
local checksum
checksum=$(grep "${filename}" "${temp_checksums}" 2>/dev/null | awk '{print $1}' | head -n1)
rm -f "${temp_checksums}"
echo "${checksum}"
else
rm -f "${temp_checksums}"
echo ""
fi
}
# 从 SHASUMS256.txt 文件获取校验和(用于 Node.js)
get_shasums256_checksum() {
local shasums_url=$1
local filename=$2
local temp_shasums
temp_shasums=$(mktemp)
if wget -q --timeout=30 -O "${temp_shasums}" "${shasums_url}" 2>/dev/null; then
local checksum
checksum=$(grep "${filename}" "${temp_shasums}" 2>/dev/null | awk '{print $1}' | head -n1)
rm -f "${temp_shasums}"
echo "${checksum}"
else
rm -f "${temp_shasums}"
echo ""
fi
}
# 验证文件完整性
verify_file_integrity() {
local file=$1
local expected_checksum=$2
local min_size=${3:-1024}
if [ ! -f "${file}" ] || [ ! -s "${file}" ]; then
printf " %s 文件不存在或为空: %s\n" "$(status_fail)" "$(basename "${file}")"
return 1
fi
local file_size
file_size=$(stat -c%s "${file}" 2>/dev/null || echo 0)
if [ -n "${min_size}" ] && [ "${file_size}" -lt "${min_size}" ]; then
printf " %s 文件过小: %s 字节 (最小: %s)\n" "$(status_fail)" "${file_size}" "${min_size}"
return 1
fi
if [ -n "${expected_checksum}" ]; then
local actual_checksum
actual_checksum=$(compute_sha256 "${file}")
if [ "${actual_checksum}" != "${expected_checksum}" ]; then
printf " %s 校验和不匹配\n" "$(status_fail)"
printf " 预期: %s\n" "${expected_checksum}"
printf " 实际: %s\n" "${actual_checksum}"
return 1
fi
printf " %s 校验和验证通过\n" "$(status_ok)"
fi
return 0
}
# 下载文件(带重试)
download_file() {
local url=$1
local output_file=$2
local desc=$3
local expected_checksum=$4
local min_size=${5:-1024}
local max_retries=3
local retry_count=0
if file_exists "${output_file}"; then
if [ -n "${expected_checksum}" ]; then
if verify_file_integrity "${output_file}" "${expected_checksum}" "${min_size}"; then
print_status_skip "$(basename "${output_file}")" "已验证"
return 0
else
printf " %s 文件损坏,重新下载...\n" "$(status_warn)"
rm -f "${output_file}"
fi
else
if verify_file_integrity "${output_file}" "" "${min_size}"; then
print_status_skip "$(basename "${output_file}")"
return 0
fi
fi
fi
while [ $retry_count -lt $max_retries ]; do
print_status_get "${desc}"
printf " %s 下载地址: %s\n" "$(status_info)" "${url}"
local wget_exit_code=0
if [ -f "${output_file}" ] && [ -s "${output_file}" ]; then
# 文件存在但校验失败,使用断点续传
wget -c --tries=3 --waitretry=2 --show-progress \
--connect-timeout=10 --timeout=300 \
-O "${output_file}" "${url}" 2>&1 || wget_exit_code=$?
else
wget --tries=3 --waitretry=2 --show-progress \
--connect-timeout=10 --timeout=300 \
-O "${output_file}" "${url}" 2>&1 || wget_exit_code=$?
fi
if [ ${wget_exit_code} -eq 0 ]; then
if verify_file_integrity "${output_file}" "${expected_checksum}" "${min_size}"; then
print_status_ok "${desc}"
return 0
else
((retry_count++))
if [ $retry_count -lt $max_retries ]; then
print_status_retry "下载" "${retry_count}"
sleep 2
rm -f "${output_file}"
fi
fi
else
((retry_count++))
if [ $retry_count -lt $max_retries ]; then
print_status_retry "下载失败 (wget=${wget_exit_code})" "${retry_count}"
sleep 2
fi
fi
done
print_status_fail "${desc}" "已重试 ${max_retries}"
rm -f "${output_file}"
return 1
}
# 克隆仓库
clone_repo() {
local url=$1
local dest_dir=$2
local branch=$3
local desc=$4
if dir_valid "${dest_dir}"; then
print_status_skip "${desc}"
return 0
fi
rm -rf "${dest_dir}" 2>/dev/null
printf " %s %s\n" "$(text_cyan "⟳ ")" "${desc}"
local clone_args=("--depth=1")
[ -n "${branch}" ] && clone_args+=("--branch" "${branch}")
if git clone "${clone_args[@]}" "${url}" "${dest_dir}" && dir_valid "${dest_dir}"; then
rm -rf "${dest_dir}/.git"
print_status_ok "${desc}"
return 0
fi
print_status_fail "${desc}"
rm -rf "${dest_dir}"
return 1
}
# ============================================================================
# VSCode 扩展函数
# ============================================================================
# 从 .vsix 文件中提取扩展版本
get_vscode_extension_version() {
local vsix_file=$1
if [ -f "${vsix_file}" ]; then
unzip -p "${vsix_file}" "extension/package.json" 2>/dev/null | \
grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | \
cut -d'"' -f4 | head -n1
else
echo ""
fi
}
# 从 Marketplace API 获取扩展的最新版本
get_vscode_latest_version() {
local extension_id=$1
local publisher
local extension_name
publisher=${extension_id%%.*}
extension_name=${extension_id#*.}
local api_url="https://marketplace.visualstudio.com/items?itemName=${extension_id}"
# 从 Marketplace 页面提取版本号
wget -q --timeout=30 -U "Mozilla/5.0" -O - "${api_url}" 2>/dev/null | \
grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | \
cut -d'"' -f4 | head -n1
}
# ============================================================================
# NPM 包版本函数
# ============================================================================
# 从 .tgz 文件中提取 npm 包版本
get_npm_package_version() {
local tgz_file=$1
if [ -f "${tgz_file}" ]; then
# npm pack 生成的文件结构为 package/package.json
tar -xzf "${tgz_file}" -O package/package.json 2>/dev/null | \
grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | \
cut -d'"' -f4 | head -n1
else
echo ""
fi
}
# 从 npm registry 获取包的最新版本
get_npm_latest_version() {
local package_name=$1
local registry=${2:-"https://registry.npmmirror.com"}
# 使用 npm view 命令获取版本
npm view "${package_name}" version --registry "${registry}" 2>/dev/null | head -n1
}
# ============================================================================
# 下载状态输出函数
# ============================================================================
# 统一的下载状态输出
print_status_check() {
local item=$1
printf " %s %s\n" "$(status_info)" "${item}"
}
print_status_get() {
local item=$1
printf " %s %s\n" "$(text_cyan "↓ ")" "${item}"
}
print_status_skip() {
local item=$1
local version=${2:-""}
if [ -n "${version}" ]; then
printf " %s %s (版本: %s)\n" "$(status_ok)" "${item}" "${version}"
else
printf " %s %s\n" "$(status_ok)" "${item}"
fi
}
print_status_update() {
local item=$1
local old_version=$2
local new_version=$3
printf " %s %s: %s → %s\n" "$(text_yellow "↻ ")" "${item}" "${old_version}" "${new_version}"
}
print_status_ok() {
local item=$1
printf " %s %s\n" "$(status_ok)" "${item}"
}
print_status_fail() {
local item=$1
local reason=${2:-""}
if [ -n "${reason}" ]; then
printf " %s %s (%s)\n" "$(status_fail)" "${item}" "${reason}"
else
printf " %s %s\n" "$(status_fail)" "${item}"
fi
}
print_status_warn() {
local item=$1
local message=${2:-""}
if [ -n "${message}" ]; then
printf " %s %s: %s\n" "$(status_warn)" "${item}" "${message}"
else
printf " %s %s\n" "$(status_warn)" "${item}"
fi
}
print_status_retry() {
local item=$1
local count=$2
printf " %s %s - 第 %s 次重试...\n" "$(status_warn)" "${item}" "${count}"
}
print_status_info() {
local message=$1
printf " %s %s\n" "$(status_info)" "${message}"
}
print_step_extra() {
local label=${1:-"+1"}
printf "%s " "$(text_yellow "[${label}]")"
}
# ============================================================================
# 导出所有函数
# ============================================================================
export -f status_ok status_warn status_miss status_info status_fail
export -f text_green text_yellow text_red text_blue text_cyan text_gray text_bold text_purple text_reset
export -f print_header print_section print_step print_status
export -f append_config check_tool check_config test_url calc_width
export -f file_exists glob_exists dir_valid
export -f compute_sha256 get_github_release_checksum get_shasums256_checksum verify_file_integrity download_file clone_repo
export -f get_vscode_extension_version get_vscode_latest_version
export -f get_npm_package_version get_npm_latest_version
export -f print_status_check print_status_get print_status_skip print_status_update print_status_ok print_status_fail print_status_warn print_status_retry print_step_extra
+9
View File
@@ -0,0 +1,9 @@
{
"$schema": "https://opencode.ai/config.json",
"mcp": {},
"provider": {
"zhipuai-coding-plan": {}
},
"model": "zhipuai-coding-plan/glm-5.1",
"small_model": "zhipuai-coding-plan/glm-4.5-air"
}
+1034
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
get_in_path_except_current() {
which -a "$1" | grep -A1 "$0" | grep -v "$0"
}
code="$(get_in_path_except_current code)"
if [ -n "$code" ]; then
exec "$code" "$@"
elif [ "$(command -v code-insiders)" ]; then
exec code-insiders "$@"
fi
# Fallback: search for vscode-server remote-cli (DevContainer 环境)
for dir in "${HOME}/.vscode-server/bin"/*/bin/remote-cli \
"/root/.vscode-server/bin"/*/bin/remote-cli \
"${HOME}/.cursor-server/bin"/*/bin/remote-cli; do
if [ -x "$dir/code" ]; then
exec "$dir/code" "$@"
fi
done
echo "code or code-insiders is not installed" >&2
exit 127
+58
View File
@@ -0,0 +1,58 @@
{
"numStartups": 80,
"installMethod": "global",
"hasSeenTasksHint": true,
"autoInstallIdeExtension": false,
"cachedStatsigGates": {
"tengu_prompt_suggestion": false
},
"mcpServers": {
"zai-mcp-server": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@z_ai/mcp-server"
],
"env": {
"Z_AI_MODE": "ZHIPU",
"Z_AI_API_KEY": "{{your_api_key}}"
}
},
"web-search-prime": {
"type": "http",
"url": "https://open.bigmodel.cn/api/mcp/web_search_prime/mcp",
"headers": {
"Authorization": "Bearer {{your_api_key}}"
}
},
"web-reader": {
"type": "http",
"url": "https://open.bigmodel.cn/api/mcp/web_reader/mcp",
"headers": {
"Authorization": "Bearer {{your_api_key}}"
}
},
"zread": {
"type": "http",
"url": "https://open.bigmodel.cn/api/mcp/zread/mcp",
"headers": {
"Authorization": "Bearer {{your_api_key}}"
}
}
},
"firstStartTime": "2026-01-01T01:02:03.004Z",
"unpinOpus47LaunchEffort": true,
"opusProMigrationComplete": true,
"sonnet1m45MigrationComplete": true,
"thinkingMigrationComplete": true,
"hasCompletedOnboarding": true,
"migrationVersion": 11,
"lastReleaseNotesSeen": "2.1.112",
"hasIdeOnboardingBeenShown": {
"vscode": true
},
"officialMarketplaceAutoInstallAttempted": true,
"officialMarketplaceAutoInstalled": true,
"showSpinnerTree": false
}
+136
View File
@@ -0,0 +1,136 @@
# ============================================================================
# DevContainer .bashrc 追加配置
# 由 Dockerfile 通过 sed 替换占位符后追加到 ~/.bashrc
# ============================================================================
# 本地二进制路径(必须优先于 NVM)
export PATH="$HOME/.local/bin:$PATH"
# NVM 配置
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Node.js 镜像配置
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node
export npm_config_registry=https://registry.npmmirror.com
export YARN_REGISTRY=https://registry.npmmirror.com
export ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
# Python/UV 配置
export PATH="{{NODE_PATH}}:{{BIN_PATH}}:$PATH"
export UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
export UV_LINK_MODE=copy
export PYTHONUNBUFFERED=1
alias uv-python='uv run python'
alias uv-pip='uv run pip'
# 语言和区域设置
export LC_ALL=zh_CN.UTF-8
export LC_TIME=zh_CN.UTF-8
export LANG=zh_CN.UTF-8
# 其他配置
export COREPACK_ENABLE_DOWNLOAD_PROMPT=false
export EDITOR=vim
export VISUAL=vim
export LESSCHARSET=utf-8
# Jujutsu (jj) 补全
if command -v jj >/dev/null 2>&1; then
source <(jj util completion bash 2>/dev/null)
fi
# 别名配置
alias ll='ls -alhF --color=auto'
alias date='date "+%F %T %p %A %B"'
export TIME_STYLE='+%Y-%m-%d %H:%M:%S'
# Python 路径
export PYTHONPATH=/workspace:$PYTHONPATH
# NVM IO.js 镜像
export NVM_IOJS_ORG_MIRROR="https://iojs.org/dist"
# 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'
# OpenCode 启动函数(参数透传至 runoc.sh)
oc() { {{BIN_PATH}}/runoc.sh "$@"; }
# 模型快捷方式: oc51, oc 51 ...
alias oc45='oc 45'
alias oc45a='oc 45a'
alias oc45f='oc 45f'
alias oc46='oc 46'
alias oc46v='oc 46v'
alias oc46vf='oc 46vf'
alias oc47='oc 47'
alias oc5='oc 5'
alias oc5t='oc 5t'
alias oc51='oc 51'
# 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
_oc_model="$(tmux show-option -pv @opencode_model 2>/dev/null || true)"
if [ -n "$_oc_model" ]; then
export OPENCODE_MODEL="$_oc_model"
fi
unset _oc_model
fi
# Docker 组提示(仅在需要时显示)
if [ "${DOCKER_ENABLED:-true}" = "true" ] && [ -S /var/run/docker.sock ] && ! groups | grep -q docker; then
export DOCKER_GROUP_REQUIRED=1
fi
# ============================================================================
# VCS 感知提示符(覆盖 devcontainer 默认,支持 jj bookmark 显示)
# ============================================================================
# 在 bashrc 加载时检测 jj 仓库(只需一次,避免每次提示都调用 jj root)
__JJ_REPO=0
if command -v jj >/dev/null 2>&1 && jj root >/dev/null 2>&1; then
__JJ_REPO=1
fi
__vcs_prompt() {
local userpart='`export XIT=$? \
&& [ ! -z "${GITHUB_USER:-}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER:-} " || echo -n "\[\033[0;32m\]\u " \
&& [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`'
local vcsbranch='`\
if [ "${__JJ_REPO:-0}" = "1" ]; then \
BRANCH=$(jj bookmark list -r @- 2>/dev/null | sed "s/[*:].*//" | head -1); \
if [ -z "${BRANCH:-}" ]; then \
BRANCH=$(jj log -r "@-" --no-graph -T "change_id.short()" 2>/dev/null); \
fi; \
if [ -n "${BRANCH:-}" ]; then \
echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}\[\033[0;36m\]) "; \
fi; \
else \
BRANCH="$(git --no-optional-locks symbolic-ref --short HEAD 2>/dev/null || git --no-optional-locks rev-parse --short HEAD 2>/dev/null)"; \
if [ -n "${BRANCH:-}" ]; then \
echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}\[\033[0;36m\]) "; \
fi; \
fi`'
local lightblue='\[\033[1;34m\]'
local removecolor='\[\033[0m\]'
PS1="${userpart} ${lightblue}\w ${vcsbranch}${removecolor}\$ "
}
__vcs_prompt
+85
View File
@@ -0,0 +1,85 @@
#!/bin/bash
# Claude Code 启动脚本,支持通过参数选择 GLM 模型版本
#
# 固定版本: 2.1.112 (来自 /workspace/.devcontainer/.cache/npm/anthropic-ai-claude-code-2.1.112.tgz)
# 注意: 不再执行自动更新,以保持版本稳定
SETTINGS_FILE="/workspace/.devcontainer/.volumes/claude/settings.json"
# 参数解析: runcc.sh [--name <名称>] [模型]
SESSION_NAME=""
MODEL_KEY=""
while [ $# -gt 0 ]; do
case "$1" in
--name|-n)
[ $# -ge 2 ] || { echo "错误: --name 需要参数" >&2; exit 1; }
SESSION_NAME="$2"; shift 2
;;
-h|--help)
echo "用法: runcc.sh [--name <名称>] [模型]"
echo ""
echo "选项:"
echo " --name, -n <名称> Claude Code 会话显示名称"
echo ""
echo "模型: 45|45a|46|47|5|5t|51 (默认 51)"
exit 0
;;
*)
MODEL_KEY="$1"; shift
;;
esac
done
MODEL_KEY="${MODEL_KEY:-51}" # 默认使用 glm-5.1
case "$MODEL_KEY" in
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" ;;
*)
echo "未知模型: $MODEL_KEY (可选: 45|45a|46|47|5|5t|51)"
exit 1
;;
esac
# 从 settings.json 中移除模型相关配置(避免覆盖 shell 环境变量)
if command -v jq &>/dev/null && [ -f "$SETTINGS_FILE" ]; then
jq 'del(.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
# ── 模型环境变量(三个层级统一为同一模型)────────────────
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}')"
# 层级 2: session 级环境变量(不同 session 隔离)
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"
# 层级 3: pane 级选项(同 session 不同 pane 隔离)
# .bashrc 会在新 shell 启动时读取此选项,覆盖 session 级设置
tmux set-option -p @anthropic_model "$MODEL"
fi
# 构建启动参数
CC_ARGS=(--ide --dangerously-skip-permissions --allow-dangerously-skip-permissions)
# --name 不可靠(不写 custom-title/agent-name JSONL 记录),
# 改用 /rename 斜杠命令作为初始提示,确保名称正确持久化
if [ -n "$SESSION_NAME" ]; then
INITIAL_PROMPT="/rename $SESSION_NAME"
fi
echo "启动 Claude Code (模型: $MODEL${SESSION_NAME:+, 会话: $SESSION_NAME})"
claude "${CC_ARGS[@]}" ${INITIAL_PROMPT:+"$INITIAL_PROMPT"}
+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
# OpenCode 启动脚本,支持通过参数选择 GLM 模型版本
#
# 用法: runoc.sh [模型]
#
# 模型: 45|45a|45f|46|46v|46vf|47|5|5t|51 (默认 51)
set -euo pipefail
PROJECT_CONFIG="/workspace/opencode.json"
GLOBAL_CONFIG="$HOME/.config/opencode/opencode.json"
# 参数解析
MODEL_KEY=""
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
echo "用法: runoc.sh [模型]"
echo ""
echo "模型: 45|45a|45f|46|46v|46vf|47|5|5t|51 (默认 51)"
exit 0
;;
*)
MODEL_KEY="$1"; shift
;;
esac
done
MODEL_KEY="${MODEL_KEY:-51}"
case "$MODEL_KEY" in
45) MODEL="zhipuai-coding-plan/glm-4.5" ;;
45a) MODEL="zhipuai-coding-plan/glm-4.5-air" ;;
45f) MODEL="zhipuai-coding-plan/glm-4.5-flash" ;;
46) MODEL="zhipuai-coding-plan/glm-4.6" ;;
46v) MODEL="zhipuai-coding-plan/glm-4.6v" ;;
46vf) MODEL="zhipuai-coding-plan/glm-4.6v-flash" ;;
47) MODEL="zhipuai-coding-plan/glm-4.7" ;;
5) MODEL="zhipuai-coding-plan/glm-5" ;;
5t) MODEL="zhipuai-coding-plan/glm-5-turbo" ;;
51) MODEL="zhipuai-coding-plan/glm-5.1" ;;
*)
echo "未知模型: $MODEL_KEY (可选: 45|45a|45f|46|46v|46vf|47|5|5t|51)"
exit 1
;;
esac
# ── tmux 模型传播(确保子 pane 能获取正确模型)─────────
if [ -n "${TMUX:-}" ]; then
SESSION_ID="$(tmux display-message -p '#{session_id}')"
# session 级环境变量
tmux setenv -t "$SESSION_ID" OPENCODE_MODEL "$MODEL"
# pane 级选项(.bashrc 会在新 shell 启动时读取)
tmux set-option -p @opencode_model "$MODEL"
fi
# ── OpenCode 自更新检查(每日一次)──
LAST_UPDATE_FILE="$HOME/.cache/opencode_update_last_run"
TODAY=$(date +%Y-%m-%d)
mkdir -p "$(dirname "$LAST_UPDATE_FILE")"
if [ ! -f "$LAST_UPDATE_FILE" ] || [ "$(cat "$LAST_UPDATE_FILE" 2>/dev/null)" != "$TODAY" ]; then
opencode upgrade 2>/dev/null || true
echo "$TODAY" > "$LAST_UPDATE_FILE"
fi
# ── 构建启动参数 ──
OC_ARGS=(-m "$MODEL")
echo "启动 OpenCode (模型: $MODEL)"
opencode "${OC_ARGS[@]}"
+25
View File
@@ -0,0 +1,25 @@
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "{{your_api_key}}",
"ANTHROPIC_BASE_URL": "https://open.bigmodel.cn/api/anthropic",
"API_TIMEOUT_MS": "3000000",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "200000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1",
"CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "75",
"CLAUDE_CODE_NO_FLICKER": "1"
},
"enabledPlugins": {
"code-simplifier@claude-plugins-official": true,
"code-review@claude-plugins-official": true,
"hookify@claude-plugins-official": true,
"ralph-loop@claude-plugins-official": true,
"skill-creator@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true,
"pyright-lsp@claude-plugins-official": true,
"superpowers@claude-plugins-official": true,
"glm-plan-usage@zai-coding-plugins": true
},
"language": "Chinese",
"skipDangerousModePermissionPrompt": true
}