chore: initialize qiming workspace repository
This commit is contained in:
149
qimingclaw/scripts/check-sidecars.sh
Normal file
149
qimingclaw/scripts/check-sidecars.sh
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/bin/bash
|
||||
# 检查 sidecar 二进制是否齐全
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/check-sidecars.sh
|
||||
# ./scripts/check-sidecars.sh --target x86_64-pc-windows-msvc
|
||||
# ./scripts/check-sidecars.sh --all-common
|
||||
# ./scripts/check-sidecars.sh --component mcp-proxy --component node-runtime
|
||||
# ./scripts/check-sidecars.sh --downloaded-only
|
||||
# ./scripts/check-sidecars.sh --dir .cache/sidecars --downloaded-only --all-common
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CHECK_DIR="${CHECK_DIR:-.cache/sidecars}"
|
||||
|
||||
TARGET=""
|
||||
TARGETS=()
|
||||
COMPONENTS=()
|
||||
DOWNLOADED_ONLY=0
|
||||
ALL_COMMON=0
|
||||
|
||||
COMMON_TARGETS=(
|
||||
"x86_64-pc-windows-msvc"
|
||||
"aarch64-pc-windows-msvc"
|
||||
"x86_64-apple-darwin"
|
||||
"aarch64-apple-darwin"
|
||||
"x86_64-unknown-linux-gnu"
|
||||
"aarch64-unknown-linux-gnu"
|
||||
)
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--target)
|
||||
TARGETS+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--all-common)
|
||||
ALL_COMMON=1
|
||||
shift 1
|
||||
;;
|
||||
--component)
|
||||
COMPONENTS+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--downloaded-only)
|
||||
DOWNLOADED_ONLY=1
|
||||
shift 1
|
||||
;;
|
||||
--dir)
|
||||
CHECK_DIR="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '1,12p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ $ALL_COMMON -eq 1 ]]; then
|
||||
TARGETS=("${COMMON_TARGETS[@]}")
|
||||
fi
|
||||
|
||||
if [[ ${#TARGETS[@]} -eq 0 ]]; then
|
||||
# Default to current platform
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
case "$(uname -m)" in
|
||||
arm64) TARGETS=("aarch64-apple-darwin") ;;
|
||||
*) TARGETS=("x86_64-apple-darwin") ;;
|
||||
esac
|
||||
;;
|
||||
Linux)
|
||||
case "$(uname -m)" in
|
||||
aarch64) TARGETS=("aarch64-unknown-linux-gnu") ;;
|
||||
armv7l) TARGETS=("aarch64-unknown-linux-gnu") ;;
|
||||
*) TARGETS=("x86_64-unknown-linux-gnu") ;;
|
||||
esac
|
||||
;;
|
||||
MINGW*|MSYS*|CYGWIN*)
|
||||
TARGETS=("x86_64-pc-windows-msvc")
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ ${#COMPONENTS[@]} -eq 0 && $DOWNLOADED_ONLY -eq 1 ]]; then
|
||||
COMPONENTS=("mcp-proxy" "node-runtime")
|
||||
fi
|
||||
|
||||
if [[ ${#COMPONENTS[@]} -eq 0 ]]; then
|
||||
COMPONENTS=("qiming-lanproxy" "mcp-proxy" "node-runtime")
|
||||
fi
|
||||
|
||||
if [[ ! -d "$CHECK_DIR" ]]; then
|
||||
echo "目录不存在: $CHECK_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
expected_filename() {
|
||||
local component="$1"
|
||||
local target="$2"
|
||||
local ext=""
|
||||
if [[ "$target" == *windows* ]]; then
|
||||
ext=".exe"
|
||||
fi
|
||||
echo "${component}-${target}${ext}"
|
||||
}
|
||||
|
||||
total_missing=0
|
||||
for TARGET in "${TARGETS[@]}"; do
|
||||
echo "检查目标: $TARGET"
|
||||
echo "检查目录: $CHECK_DIR"
|
||||
echo "组件: ${COMPONENTS[*]}"
|
||||
echo ""
|
||||
|
||||
missing=0
|
||||
for component in "${COMPONENTS[@]}"; do
|
||||
fname="$(expected_filename "$component" "$TARGET")"
|
||||
fpath="$CHECK_DIR/$fname"
|
||||
if [[ -f "$fpath" ]]; then
|
||||
echo "OK $fname"
|
||||
else
|
||||
echo "MISS $fname"
|
||||
missing=$((missing + 1))
|
||||
total_missing=$((total_missing + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [[ $missing -gt 0 ]]; then
|
||||
echo "目标 $TARGET 缺失 sidecar 文件数量: $missing"
|
||||
else
|
||||
echo "目标 $TARGET sidecar 文件检查通过"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
if [[ $total_missing -gt 0 ]]; then
|
||||
echo "总缺失 sidecar 文件数量: $total_missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "所有目标 sidecar 文件检查通过"
|
||||
59
qimingclaw/scripts/clean-downloaded-sidecars.sh
Normal file
59
qimingclaw/scripts/clean-downloaded-sidecars.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# 清理本地下载的 sidecar(二进制缓存)
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/clean-downloaded-sidecars.sh
|
||||
# ./scripts/clean-downloaded-sidecars.sh --dry-run
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CACHE_DIR="$ROOT_DIR/.cache/sidecars"
|
||||
|
||||
DRY_RUN=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift 1
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '1,12p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
targets=()
|
||||
if [[ -d "$CACHE_DIR" ]]; then
|
||||
targets+=(
|
||||
"$CACHE_DIR"/mcp-proxy-*
|
||||
"$CACHE_DIR"/node-runtime-*
|
||||
"$CACHE_DIR"/sidecar-download-manifest.txt
|
||||
)
|
||||
fi
|
||||
|
||||
echo "清理目录: $CACHE_DIR"
|
||||
removed=0
|
||||
for f in "${targets[@]}"; do
|
||||
if [[ -e "$f" ]]; then
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo "DRY rm -f $f"
|
||||
else
|
||||
rm -f "$f"
|
||||
echo "REMOVED $f"
|
||||
fi
|
||||
removed=$((removed + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $removed -eq 0 ]]; then
|
||||
echo "没有可清理的下载产物"
|
||||
else
|
||||
echo "完成,处理文件数: $removed"
|
||||
fi
|
||||
201
qimingclaw/scripts/download-node.sh
Normal file
201
qimingclaw/scripts/download-node.sh
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# download-node.sh — 下载 Node.js 到 Electron 资源目录
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/download-node.sh # 自动检测当前平台
|
||||
# ./scripts/download-node.sh darwin arm64 # 指定平台和架构
|
||||
# ./scripts/download-node.sh linux x64
|
||||
# ./scripts/download-node.sh win x64
|
||||
#
|
||||
# 支持平台/架构:
|
||||
# darwin arm64 | x64
|
||||
# linux x64 | arm64
|
||||
# win x64
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NODE_VERSION="22.14.0"
|
||||
RESOURCE_DIR="crates/agent-electron-client/resources/node"
|
||||
|
||||
# 项目根目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
TARGET_DIR="${PROJECT_ROOT}/${RESOURCE_DIR}"
|
||||
|
||||
# === 平台检测 ===
|
||||
|
||||
detect_platform() {
|
||||
local uname_s
|
||||
uname_s="$(uname -s)"
|
||||
case "${uname_s}" in
|
||||
Darwin) echo "darwin" ;;
|
||||
Linux) echo "linux" ;;
|
||||
MINGW*|MSYS*|CYGWIN*) echo "win" ;;
|
||||
*) echo "unsupported"; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_arch() {
|
||||
local uname_m
|
||||
uname_m="$(uname -m)"
|
||||
case "${uname_m}" in
|
||||
x86_64|amd64) echo "x64" ;;
|
||||
arm64|aarch64) echo "arm64" ;;
|
||||
*) echo "unsupported"; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
PLATFORM="${1:-$(detect_platform)}"
|
||||
ARCH="${2:-$(detect_arch)}"
|
||||
|
||||
echo "==> Node.js v${NODE_VERSION} for ${PLATFORM}-${ARCH}"
|
||||
|
||||
# === 构建下载 URL ===
|
||||
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
FILENAME="node-v${NODE_VERSION}-win-${ARCH}.zip"
|
||||
URL="https://nodejs.org/dist/v${NODE_VERSION}/${FILENAME}"
|
||||
else
|
||||
FILENAME="node-v${NODE_VERSION}-${PLATFORM}-${ARCH}.tar.xz"
|
||||
URL="https://nodejs.org/dist/v${NODE_VERSION}/${FILENAME}"
|
||||
fi
|
||||
|
||||
# === 检查是否已下载 ===
|
||||
|
||||
NODE_BIN="${TARGET_DIR}/bin/node"
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
NODE_BIN="${TARGET_DIR}/node.exe"
|
||||
fi
|
||||
|
||||
if [ -f "${NODE_BIN}" ]; then
|
||||
EXISTING_VERSION="$("${NODE_BIN}" --version 2>/dev/null || true)"
|
||||
if [ "${EXISTING_VERSION}" = "v${NODE_VERSION}" ]; then
|
||||
# macOS: 还需验证架构匹配,避免 universal 构建时跳过不同架构的下载
|
||||
if [ "${PLATFORM}" = "darwin" ]; then
|
||||
case "${ARCH}" in
|
||||
arm64) EXPECTED_ARCH="arm64" ;;
|
||||
x64) EXPECTED_ARCH="x86_64" ;;
|
||||
esac
|
||||
ACTUAL_ARCH="$(file "${NODE_BIN}" | grep -oE 'arm64|x86_64' | head -1 || true)"
|
||||
if [ "${ACTUAL_ARCH}" != "${EXPECTED_ARCH}" ]; then
|
||||
echo "==> Node.js v${NODE_VERSION} exists but architecture mismatch (have ${ACTUAL_ARCH}, need ${EXPECTED_ARCH}), re-downloading..."
|
||||
else
|
||||
echo "==> Node.js v${NODE_VERSION} (${ARCH}) already exists at ${TARGET_DIR}, skipping download"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "==> Node.js v${NODE_VERSION} already exists at ${TARGET_DIR}, skipping download"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# === 下载 ===
|
||||
|
||||
TMPDIR_DL="$(mktemp -d)"
|
||||
trap 'rm -rf "${TMPDIR_DL}"' EXIT
|
||||
|
||||
echo "==> Downloading ${URL}"
|
||||
if command -v curl &>/dev/null; then
|
||||
curl -fSL --progress-bar -o "${TMPDIR_DL}/${FILENAME}" "${URL}"
|
||||
elif command -v wget &>/dev/null; then
|
||||
wget -q --show-progress -O "${TMPDIR_DL}/${FILENAME}" "${URL}"
|
||||
else
|
||||
echo "Error: curl or wget required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# === 解压 ===
|
||||
|
||||
echo "==> Extracting..."
|
||||
EXTRACT_DIR="${TMPDIR_DL}/extracted"
|
||||
mkdir -p "${EXTRACT_DIR}"
|
||||
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
# Windows: 尝试多种解压方式
|
||||
if command -v unzip &>/dev/null; then
|
||||
unzip -q "${TMPDIR_DL}/${FILENAME}" -d "${EXTRACT_DIR}"
|
||||
elif command -v pwsh &>/dev/null || command -v powershell &>/dev/null; then
|
||||
# PowerShell 7+ 支持 tar 格式
|
||||
pwsh -Command "Expand-Archive -Path '${TMPDIR_DL}/${FILENAME}' -DestinationPath '${EXTRACT_DIR}' -Force" 2>/dev/null || \
|
||||
powershell -Command "Expand-Archive -Path '${TMPDIR_DL}/${FILENAME}' -DestinationPath '${EXTRACT_DIR}' -Force"
|
||||
elif command -v tar &>/dev/null; then
|
||||
# 某些 Windows 环境下的 tar 也可以处理 zip
|
||||
tar -xf "${TMPDIR_DL}/${FILENAME}" -C "${EXTRACT_DIR}"
|
||||
else
|
||||
echo "Error: No extraction tool available (unzip, powershell, or tar)" >&2
|
||||
exit 1
|
||||
fi
|
||||
INNER_DIR="${EXTRACT_DIR}/node-v${NODE_VERSION}-win-${ARCH}"
|
||||
else
|
||||
tar -xf "${TMPDIR_DL}/${FILENAME}" -C "${EXTRACT_DIR}"
|
||||
INNER_DIR="${EXTRACT_DIR}/node-v${NODE_VERSION}-${PLATFORM}-${ARCH}"
|
||||
fi
|
||||
|
||||
if [ ! -d "${INNER_DIR}" ]; then
|
||||
echo "Error: Expected directory ${INNER_DIR} not found after extraction" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# === 复制到资源目录(仅 bin/ 和 lib/) ===
|
||||
|
||||
echo "==> Installing to ${TARGET_DIR}"
|
||||
rm -rf "${TARGET_DIR}"
|
||||
mkdir -p "${TARGET_DIR}"
|
||||
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
# Windows: Node.js 发行包没有 bin/ 子目录,需要手动创建
|
||||
mkdir -p "${TARGET_DIR}/bin"
|
||||
# 复制可执行文件到 bin/ 目录
|
||||
cp "${INNER_DIR}/node.exe" "${TARGET_DIR}/bin/"
|
||||
for exe in npm npm.cmd npx npx.cmd corepack corepack.cmd; do
|
||||
if [ -f "${INNER_DIR}/${exe}" ]; then
|
||||
cp "${INNER_DIR}/${exe}" "${TARGET_DIR}/bin/"
|
||||
fi
|
||||
done
|
||||
# 复制 node_modules(npm 等工具需要)
|
||||
if [ -d "${INNER_DIR}/node_modules" ]; then
|
||||
mkdir -p "${TARGET_DIR}/lib"
|
||||
cp -r "${INNER_DIR}/node_modules" "${TARGET_DIR}/lib/node_modules"
|
||||
fi
|
||||
else
|
||||
# Unix: 只保留 bin/ 和 lib/(使用 cp -a 保留符号链接)
|
||||
cp -a "${INNER_DIR}/bin" "${TARGET_DIR}/bin"
|
||||
if [ -d "${INNER_DIR}/lib" ]; then
|
||||
cp -a "${INNER_DIR}/lib" "${TARGET_DIR}/lib"
|
||||
fi
|
||||
|
||||
# 确保可执行权限
|
||||
chmod 755 "${TARGET_DIR}/bin/node"
|
||||
for bin in npm npx corepack; do
|
||||
if [ -f "${TARGET_DIR}/bin/${bin}" ]; then
|
||||
chmod 755 "${TARGET_DIR}/bin/${bin}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# === 验证 ===
|
||||
|
||||
echo "==> Verifying installation..."
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
echo " (skipping verification on Windows — cross-platform build)"
|
||||
else
|
||||
INSTALLED_VERSION="$("${TARGET_DIR}/bin/node" --version)"
|
||||
if [ "${INSTALLED_VERSION}" = "v${NODE_VERSION}" ]; then
|
||||
echo "==> OK: Node.js ${INSTALLED_VERSION}"
|
||||
else
|
||||
echo "Error: Expected v${NODE_VERSION}, got ${INSTALLED_VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# === 输出大小统计 ===
|
||||
|
||||
echo "==> Size:"
|
||||
du -sh "${TARGET_DIR}/bin" 2>/dev/null || true
|
||||
du -sh "${TARGET_DIR}/lib" 2>/dev/null || true
|
||||
du -sh "${TARGET_DIR}" | sed 's/\t/ /' | awk '{print " Total: " $1}'
|
||||
|
||||
echo "==> Done"
|
||||
376
qimingclaw/scripts/download-sidecars.sh
Normal file
376
qimingclaw/scripts/download-sidecars.sh
Normal file
@@ -0,0 +1,376 @@
|
||||
#!/bin/bash
|
||||
# 下载 sidecar 二进制到本地缓存目录(多平台)
|
||||
#
|
||||
# 支持 target:
|
||||
# - x86_64-pc-windows-msvc
|
||||
# - aarch64-pc-windows-msvc
|
||||
# - x86_64-apple-darwin
|
||||
# - aarch64-apple-darwin
|
||||
# - x86_64-unknown-linux-gnu
|
||||
# - aarch64-unknown-linux-gnu
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/download-sidecars.sh # 自动从 GitHub 获取最新 mcp-proxy 版本
|
||||
# ./scripts/download-sidecars.sh --target x86_64-pc-windows-msvc
|
||||
# ./scripts/download-sidecars.sh --all-common
|
||||
# ./scripts/download-sidecars.sh --all-common
|
||||
# ./scripts/download-sidecars.sh --cache-dir .cache/sidecars
|
||||
# ./scripts/download-sidecars.sh --mcp-version 0.1.48 # 指定 mcp-proxy 版本
|
||||
# ./scripts/download-sidecars.sh --force # 强制重新下载
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CACHE_DIR="$ROOT_DIR/.cache/sidecars"
|
||||
TMP_DIR="/tmp/qiming-sidecar-download"
|
||||
|
||||
MCP_VERSION="0.1.53"
|
||||
NODE_VERSION="22.14.0"
|
||||
FORCE=0
|
||||
DRY_RUN=0
|
||||
ALL_COMMON=0
|
||||
NO_CHECK=0
|
||||
TARGETS=()
|
||||
LAST_SUCCESS_URL=""
|
||||
MCP_GITHUB_REPO="qiming-ai/mcp-proxy"
|
||||
|
||||
# 从 GitHub releases 获取最新版本号
|
||||
get_latest_mcp_version() {
|
||||
local api_url="https://api.github.com/repos/${MCP_GITHUB_REPO}/releases/latest"
|
||||
local response
|
||||
response=$(curl -fsSL "$api_url" 2>/dev/null) || {
|
||||
echo "ERROR: 无法从 GitHub 获取最新版本" >&2
|
||||
return 1
|
||||
}
|
||||
local tag_name
|
||||
tag_name=$(echo "$response" | grep -m1 '"tag_name"' | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
||||
if [[ -z "$tag_name" ]]; then
|
||||
echo "ERROR: 无法解析 GitHub response 中的 tag_name" >&2
|
||||
return 1
|
||||
fi
|
||||
# 移除前缀 'v' (如 v0.1.48 -> 0.1.48)
|
||||
echo "${tag_name#v}"
|
||||
}
|
||||
|
||||
COMMON_TARGETS=(
|
||||
"x86_64-pc-windows-msvc"
|
||||
"aarch64-pc-windows-msvc"
|
||||
"x86_64-apple-darwin"
|
||||
"aarch64-apple-darwin"
|
||||
"x86_64-unknown-linux-gnu"
|
||||
"aarch64-unknown-linux-gnu"
|
||||
)
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--target)
|
||||
TARGETS+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--all-common)
|
||||
ALL_COMMON=1
|
||||
shift 1
|
||||
;;
|
||||
--cache-dir)
|
||||
CACHE_DIR="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--mcp-version)
|
||||
MCP_VERSION="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--node-version)
|
||||
NODE_VERSION="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--force)
|
||||
FORCE=1
|
||||
shift 1
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift 1
|
||||
;;
|
||||
--no-check)
|
||||
NO_CHECK=1
|
||||
shift 1
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '1,28p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ $ALL_COMMON -eq 1 ]]; then
|
||||
TARGETS=("${COMMON_TARGETS[@]}")
|
||||
fi
|
||||
if [[ ${#TARGETS[@]} -eq 0 ]]; then
|
||||
# Default to current platform
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
case "$(uname -m)" in
|
||||
arm64) TARGETS=("aarch64-apple-darwin") ;;
|
||||
*) TARGETS=("x86_64-apple-darwin") ;;
|
||||
esac
|
||||
;;
|
||||
Linux)
|
||||
case "$(uname -m)" in
|
||||
aarch64) TARGETS=("aarch64-unknown-linux-gnu") ;;
|
||||
armv7l) TARGETS=("aarch64-unknown-linux-gnu") ;;
|
||||
*) TARGETS=("x86_64-unknown-linux-gnu") ;;
|
||||
esac
|
||||
;;
|
||||
MINGW*|MSYS*|CYGWIN*)
|
||||
TARGETS=("x86_64-pc-windows-msvc")
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# 如果未指定 MCP 版本,从 GitHub 获取最新版本
|
||||
if [[ -z "$MCP_VERSION" ]]; then
|
||||
echo "正在从 GitHub 获取 mcp-stdio-proxy 最新版本..."
|
||||
MCP_VERSION=$(get_latest_mcp_version) || exit 1
|
||||
echo "检测到最新版本: $MCP_VERSION"
|
||||
fi
|
||||
|
||||
mkdir -p "$CACHE_DIR" "$TMP_DIR"
|
||||
MANIFEST_FILE="$CACHE_DIR/sidecar-download-manifest.txt"
|
||||
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local out="$2"
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo "DRY curl -fL \"$url\" -o \"$out\""
|
||||
return 0
|
||||
fi
|
||||
curl -fL "$url" -o "$out"
|
||||
}
|
||||
|
||||
download_first_success() {
|
||||
local out="$1"
|
||||
shift
|
||||
local urls=("$@")
|
||||
local url
|
||||
local i=0
|
||||
LAST_SUCCESS_URL=""
|
||||
for url in "${urls[@]}"; do
|
||||
i=$((i + 1))
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo "DRY candidate[$i] -> $url"
|
||||
download_file "$url" "$out"
|
||||
LAST_SUCCESS_URL="$url"
|
||||
return 0
|
||||
fi
|
||||
if curl -fL "$url" -o "$out"; then
|
||||
LAST_SUCCESS_URL="$url"
|
||||
return 0
|
||||
fi
|
||||
echo " WARN 下载失败,尝试下一个候选: $url" >&2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
extract_archive() {
|
||||
local archive="$1"
|
||||
local out_dir="$2"
|
||||
rm -rf "$out_dir"
|
||||
mkdir -p "$out_dir"
|
||||
# 按扩展名选择解压方式;Windows 下 MCP 为 .zip,必须用 unzip(若保存时无扩展名会误走 tar 导致失败)
|
||||
if [[ "$archive" == *.zip ]]; then
|
||||
unzip -q -o "$archive" -d "$out_dir"
|
||||
elif [[ "$archive" == *.tar.xz || "$archive" == *.tar.gz || "$archive" == *.tgz ]]; then
|
||||
tar -xf "$archive" -C "$out_dir"
|
||||
else
|
||||
# 无扩展名或未知格式时用 file 检测
|
||||
if command -v file &>/dev/null; then
|
||||
case "$(file -b "$archive")" in
|
||||
*[Zz]ip*)
|
||||
unzip -q -o "$archive" -d "$out_dir"
|
||||
;;
|
||||
*)
|
||||
tar -xf "$archive" -C "$out_dir"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
tar -xf "$archive" -C "$out_dir"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $DRY_RUN -eq 0 ]]; then
|
||||
{
|
||||
echo "# sidecar download manifest"
|
||||
echo "# generated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
echo "# mcp_version=$MCP_VERSION node_version=$NODE_VERSION"
|
||||
echo "# cache_dir=$CACHE_DIR"
|
||||
echo "# format: target component filename source"
|
||||
} > "$MANIFEST_FILE"
|
||||
fi
|
||||
|
||||
for TARGET in "${TARGETS[@]}"; do
|
||||
MCP_ARTIFACT_CANDIDATES=()
|
||||
case "$TARGET" in
|
||||
x86_64-pc-windows-msvc)
|
||||
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-x86_64-pc-windows-msvc.zip")
|
||||
MCP_BIN_NAME="mcp-proxy.exe"
|
||||
NODE_ARTIFACT="node-v${NODE_VERSION}-win-x64.zip"
|
||||
NODE_BIN_NAME="node.exe"
|
||||
DEST_EXT=".exe"
|
||||
;;
|
||||
aarch64-pc-windows-msvc)
|
||||
MCP_ARTIFACT_CANDIDATES=(
|
||||
"mcp-stdio-proxy-aarch64-pc-windows-msvc.zip"
|
||||
"mcp-stdio-proxy-x86_64-pc-windows-msvc.zip"
|
||||
)
|
||||
MCP_BIN_NAME="mcp-proxy.exe"
|
||||
NODE_ARTIFACT="node-v${NODE_VERSION}-win-arm64.zip"
|
||||
NODE_BIN_NAME="node.exe"
|
||||
DEST_EXT=".exe"
|
||||
;;
|
||||
x86_64-apple-darwin)
|
||||
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-x86_64-apple-darwin.tar.xz")
|
||||
MCP_BIN_NAME="mcp-proxy"
|
||||
NODE_ARTIFACT="node-v${NODE_VERSION}-darwin-x64.tar.gz"
|
||||
NODE_BIN_NAME="node"
|
||||
DEST_EXT=""
|
||||
;;
|
||||
aarch64-apple-darwin)
|
||||
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-aarch64-apple-darwin.tar.xz")
|
||||
MCP_BIN_NAME="mcp-proxy"
|
||||
NODE_ARTIFACT="node-v${NODE_VERSION}-darwin-arm64.tar.gz"
|
||||
NODE_BIN_NAME="node"
|
||||
DEST_EXT=""
|
||||
;;
|
||||
x86_64-unknown-linux-gnu)
|
||||
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-x86_64-unknown-linux-gnu.tar.xz")
|
||||
MCP_BIN_NAME="mcp-proxy"
|
||||
NODE_ARTIFACT="node-v${NODE_VERSION}-linux-x64.tar.xz"
|
||||
NODE_BIN_NAME="node"
|
||||
DEST_EXT=""
|
||||
;;
|
||||
aarch64-unknown-linux-gnu)
|
||||
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-aarch64-unknown-linux-gnu.tar.xz")
|
||||
MCP_BIN_NAME="mcp-proxy"
|
||||
NODE_ARTIFACT="node-v${NODE_VERSION}-linux-arm64.tar.xz"
|
||||
NODE_BIN_NAME="node"
|
||||
DEST_EXT=""
|
||||
;;
|
||||
*)
|
||||
echo "不支持的 target: $TARGET" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
MCP_CACHE="$CACHE_DIR/mcp-proxy-${TARGET}${DEST_EXT}"
|
||||
NODE_CACHE="$CACHE_DIR/node-runtime-${TARGET}${DEST_EXT}"
|
||||
|
||||
# 从第一个候选 artifact 得到压缩包后缀(.zip 或 .tar.xz),保证 extract_archive 能正确选择 unzip/tar
|
||||
MCP_ARCHIVE_SUFFIX="${MCP_ARTIFACT_CANDIDATES[0]#*${TARGET}}"
|
||||
MCP_PKG="$TMP_DIR/mcp-stdio-proxy-${TARGET}${MCP_ARCHIVE_SUFFIX}"
|
||||
|
||||
# 缓存版本记录文件
|
||||
MCP_VERSION_FILE="$CACHE_DIR/mcp-proxy-${TARGET}.version"
|
||||
|
||||
echo "target: $TARGET"
|
||||
echo " cache dir : $CACHE_DIR"
|
||||
echo " mcp version : $MCP_VERSION"
|
||||
echo " node version: $NODE_VERSION"
|
||||
|
||||
# 检查是否需要重新下载:force标志、缓存文件不存在、或版本不一致
|
||||
NEED_DOWNLOAD=0
|
||||
if [[ $FORCE -eq 1 ]]; then
|
||||
NEED_DOWNLOAD=1
|
||||
rm -f "$MCP_VERSION_FILE"
|
||||
echo " FORCE 强制重新下载"
|
||||
elif [[ ! -f "$MCP_CACHE" || ! -f "$NODE_CACHE" ]]; then
|
||||
NEED_DOWNLOAD=1
|
||||
echo " 缓存文件不存在,需要下载"
|
||||
elif [[ -f "$MCP_VERSION_FILE" ]]; then
|
||||
CACHED_VERSION=$(cat "$MCP_VERSION_FILE" 2>/dev/null || echo "")
|
||||
if [[ "$CACHED_VERSION" != "$MCP_VERSION" ]]; then
|
||||
NEED_DOWNLOAD=1
|
||||
echo " 版本不一致 (缓存: $CACHED_VERSION, 最新: $MCP_VERSION),需要更新"
|
||||
fi
|
||||
else
|
||||
# 缓存文件存在但没有版本记录,尝试从二进制获取版本
|
||||
CACHED_VERSION=$("$MCP_CACHE" -V 2>/dev/null | awk '{print $2}' || echo "")
|
||||
if [[ -z "$CACHED_VERSION" ]]; then
|
||||
# 无法获取版本,保守起见重新下载
|
||||
NEED_DOWNLOAD=1
|
||||
echo " 无法获取缓存版本,需要重新下载"
|
||||
elif [[ "$CACHED_VERSION" != "$MCP_VERSION" ]]; then
|
||||
NEED_DOWNLOAD=1
|
||||
echo " 版本不一致 (缓存: $CACHED_VERSION, 最新: $MCP_VERSION),需要更新"
|
||||
else
|
||||
# 版本一致,补写版本记录文件
|
||||
echo "$MCP_VERSION" > "$MCP_VERSION_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $NEED_DOWNLOAD -eq 0 ]]; then
|
||||
echo " SKIP 已存在缓存: $(basename "$MCP_CACHE"), $(basename "$NODE_CACHE")"
|
||||
else
|
||||
NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/${NODE_ARTIFACT}"
|
||||
NODE_PKG="$TMP_DIR/${NODE_ARTIFACT}"
|
||||
|
||||
MCP_URLS=()
|
||||
for artifact in "${MCP_ARTIFACT_CANDIDATES[@]}"; do
|
||||
MCP_URLS+=("https://qiming-packages.oss-rg-china-mainland.aliyuncs.com/mcp-stdio-proxy/v${MCP_VERSION}/${artifact}")
|
||||
done
|
||||
|
||||
if ! download_first_success "$MCP_PKG" "${MCP_URLS[@]}"; then
|
||||
echo "下载 mcp-proxy 失败 (target=$TARGET, version=$MCP_VERSION)" >&2
|
||||
exit 1
|
||||
fi
|
||||
MCP_SELECTED_URL="$LAST_SUCCESS_URL"
|
||||
download_file "$NODE_URL" "$NODE_PKG"
|
||||
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo " DRY copy -> $MCP_CACHE"
|
||||
echo " DRY copy -> $NODE_CACHE"
|
||||
echo ""
|
||||
continue
|
||||
fi
|
||||
|
||||
MCP_EXTRACT_DIR="$TMP_DIR/extract-mcp-${TARGET}"
|
||||
NODE_EXTRACT_DIR="$TMP_DIR/extract-node-${TARGET}"
|
||||
extract_archive "$MCP_PKG" "$MCP_EXTRACT_DIR"
|
||||
extract_archive "$NODE_PKG" "$NODE_EXTRACT_DIR"
|
||||
|
||||
MCP_SRC="$(find "$MCP_EXTRACT_DIR" -type f -name "$MCP_BIN_NAME" | head -n 1)"
|
||||
NODE_SRC="$(find "$NODE_EXTRACT_DIR" -type f -name "$NODE_BIN_NAME" | head -n 1)"
|
||||
if [[ -z "$MCP_SRC" || -z "$NODE_SRC" ]]; then
|
||||
echo "解压后未找到目标二进制 (target=$TARGET)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -f "$MCP_SRC" "$MCP_CACHE"
|
||||
cp -f "$NODE_SRC" "$NODE_CACHE"
|
||||
chmod +x "$MCP_CACHE" "$NODE_CACHE" || true
|
||||
# 保存版本号到版本文件
|
||||
echo "$MCP_VERSION" > "$MCP_VERSION_FILE"
|
||||
echo " DONE cache $(basename "$MCP_CACHE")"
|
||||
echo " DONE cache $(basename "$NODE_CACHE")"
|
||||
echo "$TARGET mcp-proxy $(basename "$MCP_CACHE") $MCP_SELECTED_URL" >> "$MANIFEST_FILE"
|
||||
echo "$TARGET node-runtime $(basename "$NODE_CACHE") $NODE_URL" >> "$MANIFEST_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "完成。"
|
||||
echo " cache: $CACHE_DIR"
|
||||
|
||||
if [[ $NO_CHECK -eq 0 && $DRY_RUN -eq 0 ]]; then
|
||||
echo ""
|
||||
echo "开始自动校验下载项 sidecar..."
|
||||
for t in "${TARGETS[@]}"; do
|
||||
"$SCRIPT_DIR/check-sidecars.sh" --downloaded-only --target "$t" --dir "$CACHE_DIR"
|
||||
done
|
||||
fi
|
||||
201
qimingclaw/scripts/download-uv.sh
Normal file
201
qimingclaw/scripts/download-uv.sh
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# download-uv.sh — 下载 uv 到 Electron 资源目录
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/download-uv.sh # 自动检测当前平台
|
||||
# ./scripts/download-uv.sh darwin arm64 # 指定平台和架构
|
||||
# ./scripts/download-uv.sh linux x64
|
||||
# ./scripts/download-uv.sh win x64
|
||||
#
|
||||
# 支持平台/架构:
|
||||
# darwin arm64 | x64
|
||||
# linux x64 | arm64
|
||||
# win x64
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
UV_VERSION="0.10.0"
|
||||
RESOURCE_DIR="crates/agent-electron-client/resources/uv"
|
||||
|
||||
# 项目根目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
TARGET_DIR="${PROJECT_ROOT}/${RESOURCE_DIR}"
|
||||
|
||||
# === 平台检测 ===
|
||||
|
||||
detect_platform() {
|
||||
local uname_s
|
||||
uname_s="$(uname -s)"
|
||||
case "${uname_s}" in
|
||||
Darwin) echo "darwin" ;;
|
||||
Linux) echo "linux" ;;
|
||||
MINGW*|MSYS*|CYGWIN*) echo "win" ;;
|
||||
*) echo "unsupported"; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_arch() {
|
||||
local uname_m
|
||||
uname_m="$(uname -m)"
|
||||
case "${uname_m}" in
|
||||
x86_64|amd64) echo "x64" ;;
|
||||
arm64|aarch64) echo "arm64" ;;
|
||||
*) echo "unsupported"; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
PLATFORM="${1:-$(detect_platform)}"
|
||||
ARCH="${2:-$(detect_arch)}"
|
||||
|
||||
echo "==> uv v${UV_VERSION} for ${PLATFORM}-${ARCH}"
|
||||
|
||||
# === 构建下载 URL ===
|
||||
|
||||
# 映射到 uv release target 名称
|
||||
case "${PLATFORM}-${ARCH}" in
|
||||
darwin-arm64) UV_TARGET="uv-aarch64-apple-darwin" ;;
|
||||
darwin-x64) UV_TARGET="uv-x86_64-apple-darwin" ;;
|
||||
linux-x64) UV_TARGET="uv-x86_64-unknown-linux-gnu" ;;
|
||||
linux-arm64) UV_TARGET="uv-aarch64-unknown-linux-gnu" ;;
|
||||
win-x64) UV_TARGET="uv-x86_64-pc-windows-msvc" ;;
|
||||
*) echo "Error: unsupported platform-arch: ${PLATFORM}-${ARCH}" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
FILENAME="${UV_TARGET}.zip"
|
||||
else
|
||||
FILENAME="${UV_TARGET}.tar.gz"
|
||||
fi
|
||||
|
||||
URL="https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/${FILENAME}"
|
||||
|
||||
# === 检查是否已下载 ===
|
||||
|
||||
UV_BIN="${TARGET_DIR}/bin/uv"
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
UV_BIN="${TARGET_DIR}/bin/uv.exe"
|
||||
fi
|
||||
|
||||
if [ -f "${UV_BIN}" ]; then
|
||||
EXISTING_VERSION="$("${UV_BIN}" --version 2>/dev/null | awk '{print $2}' || true)"
|
||||
if [ "${EXISTING_VERSION}" = "${UV_VERSION}" ]; then
|
||||
if [ "${PLATFORM}" = "darwin" ]; then
|
||||
case "${ARCH}" in
|
||||
arm64) EXPECTED_ARCH="arm64" ;;
|
||||
x64) EXPECTED_ARCH="x86_64" ;;
|
||||
esac
|
||||
ACTUAL_ARCH="$(file "${UV_BIN}" | grep -oE 'arm64|x86_64' | head -1 || true)"
|
||||
if [ "${ACTUAL_ARCH}" != "${EXPECTED_ARCH}" ]; then
|
||||
echo "==> uv v${UV_VERSION} exists but architecture mismatch (have ${ACTUAL_ARCH}, need ${EXPECTED_ARCH}), re-downloading..."
|
||||
else
|
||||
echo "==> uv v${UV_VERSION} (${ARCH}) already exists at ${TARGET_DIR}, skipping download"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "==> uv v${UV_VERSION} already exists at ${TARGET_DIR}, skipping download"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# === 下载 ===
|
||||
|
||||
TMPDIR_DL="$(mktemp -d)"
|
||||
trap 'rm -rf "${TMPDIR_DL}"' EXIT
|
||||
|
||||
echo "==> Downloading ${URL}"
|
||||
if command -v curl &>/dev/null; then
|
||||
curl -fSL --progress-bar -o "${TMPDIR_DL}/${FILENAME}" "${URL}"
|
||||
elif command -v wget &>/dev/null; then
|
||||
wget -q --show-progress -O "${TMPDIR_DL}/${FILENAME}" "${URL}"
|
||||
else
|
||||
echo "Error: curl or wget required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# === 解压 ===
|
||||
|
||||
echo "==> Extracting..."
|
||||
EXTRACT_DIR="${TMPDIR_DL}/extracted"
|
||||
mkdir -p "${EXTRACT_DIR}"
|
||||
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
# Windows: 尝试多种解压方式
|
||||
if command -v unzip &>/dev/null; then
|
||||
unzip -q "${TMPDIR_DL}/${FILENAME}" -d "${EXTRACT_DIR}"
|
||||
elif command -v pwsh &>/dev/null || command -v powershell &>/dev/null; then
|
||||
# PowerShell 7+ 支持 tar 格式
|
||||
pwsh -Command "Expand-Archive -Path '${TMPDIR_DL}/${FILENAME}' -DestinationPath '${EXTRACT_DIR}' -Force" 2>/dev/null || \
|
||||
powershell -Command "Expand-Archive -Path '${TMPDIR_DL}/${FILENAME}' -DestinationPath '${EXTRACT_DIR}' -Force"
|
||||
elif command -v tar &>/dev/null; then
|
||||
# 某些 Windows 环境下的 tar 也可以处理 zip
|
||||
tar -xf "${TMPDIR_DL}/${FILENAME}" -C "${EXTRACT_DIR}"
|
||||
else
|
||||
echo "Error: No extraction tool available (unzip, powershell, or tar)" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
tar -xzf "${TMPDIR_DL}/${FILENAME}" -C "${EXTRACT_DIR}"
|
||||
fi
|
||||
|
||||
# 定位 uv 二进制所在目录(不同平台/版本的归档结构可能不同:有或无外层子目录)
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
UV_FOUND="$(find "${EXTRACT_DIR}" -name "uv.exe" -type f | head -1)"
|
||||
else
|
||||
UV_FOUND="$(find "${EXTRACT_DIR}" -name "uv" -type f | head -1)"
|
||||
fi
|
||||
|
||||
if [ -z "${UV_FOUND}" ]; then
|
||||
echo "Error: uv binary not found after extraction" >&2
|
||||
echo "==> Contents of extract dir:" >&2
|
||||
find "${EXTRACT_DIR}" -type f >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INNER_DIR="$(dirname "${UV_FOUND}")"
|
||||
|
||||
# === 复制到资源目录 ===
|
||||
|
||||
echo "==> Installing to ${TARGET_DIR}"
|
||||
rm -rf "${TARGET_DIR}"
|
||||
mkdir -p "${TARGET_DIR}/bin"
|
||||
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
cp "${INNER_DIR}/uv.exe" "${TARGET_DIR}/bin/"
|
||||
if [ -f "${INNER_DIR}/uvx.exe" ]; then
|
||||
cp "${INNER_DIR}/uvx.exe" "${TARGET_DIR}/bin/"
|
||||
fi
|
||||
else
|
||||
cp "${INNER_DIR}/uv" "${TARGET_DIR}/bin/"
|
||||
if [ -f "${INNER_DIR}/uvx" ]; then
|
||||
cp "${INNER_DIR}/uvx" "${TARGET_DIR}/bin/"
|
||||
fi
|
||||
chmod 755 "${TARGET_DIR}/bin/uv"
|
||||
[ -f "${TARGET_DIR}/bin/uvx" ] && chmod 755 "${TARGET_DIR}/bin/uvx"
|
||||
fi
|
||||
|
||||
# === 验证 ===
|
||||
|
||||
echo "==> Verifying installation..."
|
||||
if [ "${PLATFORM}" = "win" ]; then
|
||||
echo " (skipping verification on Windows — cross-platform build)"
|
||||
else
|
||||
INSTALLED_VERSION="$("${TARGET_DIR}/bin/uv" --version | awk '{print $2}')"
|
||||
if [ "${INSTALLED_VERSION}" = "${UV_VERSION}" ]; then
|
||||
echo "==> OK: uv ${INSTALLED_VERSION}"
|
||||
else
|
||||
echo "Error: Expected ${UV_VERSION}, got ${INSTALLED_VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# === 输出大小统计 ===
|
||||
|
||||
echo "==> Size:"
|
||||
du -sh "${TARGET_DIR}/bin" 2>/dev/null || true
|
||||
du -sh "${TARGET_DIR}" | sed 's/\t/ /' | awk '{print " Total: " $1}'
|
||||
|
||||
echo "==> Done"
|
||||
80
qimingclaw/scripts/prefetch-npm-tarballs.sh
Normal file
80
qimingclaw/scripts/prefetch-npm-tarballs.sh
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
# 预下载 npm 包 tarball 到本地目录,便于后续离线/半离线安装
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/prefetch-npm-tarballs.sh
|
||||
# ./scripts/prefetch-npm-tarballs.sh --out /tmp/qiming-npm-tarballs
|
||||
# ./scripts/prefetch-npm-tarballs.sh --registry https://registry.npmmirror.com/
|
||||
# ./scripts/prefetch-npm-tarballs.sh --package qiming-file-server --package mcp-stdio-proxy
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
DEFAULT_OUT="$ROOT_DIR/.cache/npm-tarballs"
|
||||
OUT_DIR="$DEFAULT_OUT"
|
||||
REGISTRY="${QIMING_NPM_REGISTRY:-https://registry.npmmirror.com/}"
|
||||
DRY_RUN=0
|
||||
PACKAGES=()
|
||||
|
||||
DEFAULT_PACKAGES=(
|
||||
"mcp-stdio-proxy"
|
||||
"qiming-file-server"
|
||||
"qimingcode"
|
||||
"claude-code-acp-ts"
|
||||
)
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--out)
|
||||
OUT_DIR="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--registry)
|
||||
REGISTRY="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--package)
|
||||
PACKAGES+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift 1
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '1,20p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ${#PACKAGES[@]} -eq 0 ]]; then
|
||||
PACKAGES=("${DEFAULT_PACKAGES[@]}")
|
||||
fi
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
echo "npm tarball 预下载目录: $OUT_DIR"
|
||||
echo "registry: $REGISTRY"
|
||||
echo "packages: ${PACKAGES[*]}"
|
||||
echo ""
|
||||
|
||||
for pkg in "${PACKAGES[@]}"; do
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo "DRY npm pack \"$pkg\" --pack-destination \"$OUT_DIR\" --registry \"$REGISTRY\""
|
||||
continue
|
||||
fi
|
||||
|
||||
echo ">>> downloading $pkg"
|
||||
npm pack "$pkg" --pack-destination "$OUT_DIR" --registry "$REGISTRY"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "完成,已下载到: $OUT_DIR"
|
||||
ls -1 "$OUT_DIR" | sed 's/^/ - /'
|
||||
18
qimingclaw/scripts/prepare-sidecars.sh
Normal file
18
qimingclaw/scripts/prepare-sidecars.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# prepare-sidecars.sh — 此脚本已弃用
|
||||
#
|
||||
# Electron 使用 npm 脚本准备 sidecar 二进制:
|
||||
# cd crates/agent-electron-client
|
||||
# npm run prepare # 准备所有
|
||||
# npm run prepare:node # 仅 Node.js
|
||||
# npm run prepare:uv # 仅 uv
|
||||
# npm run prepare:mcp-proxy # 仅 mcp-proxy
|
||||
# npm run prepare:lanproxy # 仅 lanproxy
|
||||
# npm run prepare:qimingcode # 仅 qimingcode
|
||||
|
||||
echo "警告: 此脚本已弃用"
|
||||
echo ""
|
||||
echo "请使用 Electron 的 npm prepare 脚本:"
|
||||
echo " cd crates/agent-electron-client"
|
||||
echo " npm run prepare"
|
||||
echo ""
|
||||
117
qimingclaw/scripts/sign-macos-resource-bins.sh
Normal file
117
qimingclaw/scripts/sign-macos-resource-bins.sh
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# sign-macos-resource-bins.sh — 在 macOS 上对即将被打进 App 的第三方二进制做 codesign
|
||||
#
|
||||
# 背景:
|
||||
# Electron 打包时会把 resources 中的文件拷贝到 .app/Contents/Resources/,
|
||||
# 但不会对这些可执行文件做签名。Apple 公证要求包内所有可执行文件必须:
|
||||
# - 使用有效的 Developer ID 证书签名
|
||||
# - 包含安全时间戳 (--timestamp)
|
||||
# - 启用硬化运行时 (--options runtime)
|
||||
# 因此需在 electron-builder 打包之前,对 resources 下的 node 与 uv/uvx
|
||||
# 预先签名,这样打进包后即可通过公证。(node 与 uv 均不省略,一并签名。)
|
||||
#
|
||||
# 用法:
|
||||
# 在项目根目录执行: ./scripts/sign-macos-resource-bins.sh
|
||||
# 需设置环境变量 APPLE_SIGNING_IDENTITY,例如:
|
||||
# export APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAM_ID)"
|
||||
# make electron-bundle
|
||||
#
|
||||
# 仅在 macOS 上执行;非 macOS 或未设置 APPLE_SIGNING_IDENTITY 时直接退出 0。
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 仅 macOS 需要此步骤
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 明确说明本步骤会对哪些资源签名
|
||||
echo "==> [sign-macos-resource-bins] 将对 node、uv/uvx 做 codesign(供 macOS 公证)"
|
||||
|
||||
# 未配置签名身份时跳过(例如 CI 未配置或非发布构建)
|
||||
if [ -z "${APPLE_SIGNING_IDENTITY:-}" ]; then
|
||||
echo "==> [sign-macos-resource-bins] 未设置 APPLE_SIGNING_IDENTITY,跳过对 node/uv 的签名"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
ELECTRON_RESOURCES="${PROJECT_ROOT}/crates/agent-electron-client/resources"
|
||||
# node-runtime 为 V8/Node,需 JIT 权限,否则 macOS 上会 SIGTRAP
|
||||
NODE_RUNTIME_ENTITLEMENTS="${ELECTRON_RESOURCES}/node.entitlements"
|
||||
|
||||
# 使用一致的 identity 对单个二进制签名:Developer ID + 时间戳 + 硬化运行时
|
||||
sign_one() {
|
||||
local bin_path="$1"
|
||||
if [ ! -f "$bin_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
# 若已是同 identity 签名则可能跳过;--force 确保使用当前 identity 重签
|
||||
codesign --force --timestamp --options runtime -s "${APPLE_SIGNING_IDENTITY}" -- "$bin_path"
|
||||
echo " signed: $bin_path"
|
||||
}
|
||||
|
||||
# 对 node-runtime 使用带 JIT entitlement 的签名(仅 macOS Mach-O)
|
||||
sign_node_runtime_with_jit() {
|
||||
local bin_path="$1"
|
||||
if [ ! -f "$bin_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
if ! file "$bin_path" | grep -q "Mach-O"; then
|
||||
return 0
|
||||
fi
|
||||
codesign --force --timestamp --options runtime \
|
||||
--entitlements "${NODE_RUNTIME_ENTITLEMENTS}" \
|
||||
-s "${APPLE_SIGNING_IDENTITY}" -- "$bin_path"
|
||||
echo " signed (with JIT entitlement): $bin_path"
|
||||
}
|
||||
|
||||
echo "==> [sign-macos-resource-bins] 使用 identity: ${APPLE_SIGNING_IDENTITY}"
|
||||
|
||||
# 1. 签名 resources/uv/bin 下的 uv、uvx(公证报错即为此二者)
|
||||
UV_BIN_DIR="${ELECTRON_RESOURCES}/uv/bin"
|
||||
if [ -d "${UV_BIN_DIR}" ]; then
|
||||
echo "==> [sign-macos-resource-bins] 签名 uv 资源 (uv, uvx)..."
|
||||
for name in uv uvx; do
|
||||
[ -f "${UV_BIN_DIR}/${name}" ] && sign_one "${UV_BIN_DIR}/${name}"
|
||||
done
|
||||
else
|
||||
echo "==> [sign-macos-resource-bins] 未找到 ${UV_BIN_DIR},跳过 uv 签名"
|
||||
fi
|
||||
|
||||
# 2. 签名 resources/node/bin 下的可执行文件
|
||||
# 注意:node 是 V8/Node.js 运行时,需要 JIT 权限
|
||||
NODE_BIN_DIR="${ELECTRON_RESOURCES}/node/bin"
|
||||
if [ -d "${NODE_BIN_DIR}" ]; then
|
||||
echo "==> [sign-macos-resource-bins] 签名 node 资源 (带 JIT entitlement)..."
|
||||
for f in "${NODE_BIN_DIR}"/*; do
|
||||
if [ -f "$f" ] && [ -x "$f" ]; then
|
||||
# 排除脚本或非 Mach-O(如 .sh);仅签名可执行二进制
|
||||
if file "$f" | grep -q "Mach-O"; then
|
||||
# node 是 V8 运行时,需要 JIT 权限
|
||||
sign_node_runtime_with_jit "$f"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "==> [sign-macos-resource-bins] 未找到 ${NODE_BIN_DIR},跳过 node 签名"
|
||||
fi
|
||||
|
||||
# 3. 签名 resources/lanproxy/bin 下的 lanproxy
|
||||
LANPROXY_BIN_DIR="${ELECTRON_RESOURCES}/lanproxy/bin"
|
||||
if [ -d "${LANPROXY_BIN_DIR}" ]; then
|
||||
echo "==> [sign-macos-resource-bins] 签名 lanproxy 资源..."
|
||||
for f in "${LANPROXY_BIN_DIR}"/*; do
|
||||
if [ -f "$f" ] && [ -x "$f" ]; then
|
||||
if file "$f" | grep -q "Mach-O"; then
|
||||
sign_one "$f"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "==> [sign-macos-resource-bins] 未找到 ${LANPROXY_BIN_DIR},跳过 lanproxy 签名"
|
||||
fi
|
||||
|
||||
echo "==> [sign-macos-resource-bins] node / uv / lanproxy 资源签名完成"
|
||||
Reference in New Issue
Block a user