page / scripts /main.sh
GGSheng's picture
feat: deploy to hf space | model=claude-22327b
31b91d7 verified
Raw
History Blame Contribute Delete
25.8 kB
#!/usr/bin/env bash
# ============================================================
# OpenClaw 工具箱 — 本地开发工具集
#
# 交互式菜单,选择要执行的功能。
# 也支持命令行直接调用:./main.sh <命令> [参数...]
#
# 命令列表:
# bootstrap 交互式部署 OpenClaw 到 HF Space
# rebuild-space 推送代码到 Space 并触发重建
# restart-space 重启 Space
# pause-space 暂停 Space
# factory-rebuild Factory 重建 Space(删除重建)
# cleanup 批量清理 Dataset 中的旧备份
# find-backup 查找最佳备份(可选清理旧备份)
# rm-hf 删除 HF 仓库/文件/存储
# hf-backup HF Dataset 本地↔远程交互备份(快照/验证/清理/恢复)
# hf-account HF 多账号管理(添加/切换/删除账号)
# storage HF 存储工具(上传/下载/同步)
# ============================================================
set -euo pipefail
# ---- Fix terminal for interactive input ----
if [[ -t 0 ]]; then
stty sane 2>/dev/null || true
fi
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd)"
# 引入 HF 用户 helper
# shellcheck source=/dev/null
source "$SCRIPT_DIR/_hf_user.sh" 2>/dev/null || true
# 获取默认 HF 用户名(用于示例提示)
get_default_hf_user() {
if declare -f get_hf_username >/dev/null 2>&1; then
local u
u="$(get_hf_username 2>/dev/null)" || true
if [[ -n "$u" ]]; then
printf '%s' "$u"
return 0
fi
fi
return 1
}
# 获取默认 Space 或 Dataset 名(基于当前目录名)
get_default_repo_name() {
basename "$(pwd)" 2>/dev/null || true
}
# ---- HF 账号切换提示 ----
prompt_yes_no() {
local prompt="$1"
local default="${2:-}"
local choice=""
local answer=""
while [[ -z "$answer" ]]; do
if [[ -t 0 ]]; then
if [[ -n "$default" ]]; then
read -r -p "$prompt (Y/n) [$default]: " choice || true
else
read -r -p "$prompt (y/N) [$default]: " choice || true
fi
else
choice="$default"
fi
choice="$(printf '%s' "$choice" | tr -d '\r\n' | tr '[:upper:]' '[:lower:]')"
if [[ -z "$choice" ]]; then
choice="$default"
fi
case "$choice" in
y|yes) answer="yes" ;;
n|no) answer="no" ;;
*) printf 'Please answer y or n.\n' >&2 ;;
esac
done
printf '%s' "$answer"
}
prompt_secret() {
local prompt="$1"
local value=""
local char=""
if [[ -t 0 && -t 1 ]]; then
printf '%s: ' "$prompt" >&2
while IFS= read -r -s -n 1 char; do
if [[ -z "$char" ]]; then
break
fi
case "$char" in
$'\e') break ;;
'') break ;;
*) value+="$char"; printf '*' >&2 ;;
esac
done
printf '\n' >&2
fi
printf '%s' "$value"
}
prompt_hf_account_switch() {
local current_username=""
local hf_whoami_output
hf_whoami_output="$(hf auth whoami 2>&1 || true)"
current_username="$(printf '%s\n' "$hf_whoami_output" | sed -nE 's/^[[:space:]]*user:[[:space:]]*([^[:space:]]+).*/\1/p' | head -n 1)"
if [[ -z "$current_username" ]]; then
current_username="$(printf '%s\n' "$hf_whoami_output" | sed -nE 's/.*[Ll]ogged in as[[:space:]]+([^[:space:]]+).*/\1/p' | head -n 1)"
fi
if [[ -n "$current_username" ]] && [[ -t 0 ]]; then
local use_current
use_current="$(prompt_yes_no "HF CLI is logged in as '$current_username'. Use this user?" "y")"
if [[ "$use_current" == "yes" ]]; then
return 0
fi
fi
while true; do
if [[ -t 0 ]]; then
local token
token="$(prompt_secret "Enter HF_TOKEN to switch account")"
if [[ -z "$token" ]]; then
printf '[WARN] Empty token, please try again or press Ctrl+C to cancel\n' >&2
continue
fi
local login_output
login_output="$(hf auth login --token "$token" 2>&1)"
if [[ $? -eq 0 ]]; then
local verify_output
verify_output="$(hf auth whoami 2>&1 || true)"
local new_username
new_username="$(printf '%s\n' "$verify_output" | sed -nE 's/^[[:space:]]*user:[[:space:]]*([^[:space:]]+).*/\1/p' | head -n 1)"
if [[ -z "$new_username" ]]; then
new_username="$(printf '%s\n' "$verify_output" | sed -nE 's/.*[Ll]ogged in as[[:space:]]+([^[:space:]]+).*/\1/p' | head -n 1)"
fi
if [[ -n "$current_username" ]] && [[ -n "$new_username" ]] && [[ "$current_username" != "$new_username" ]]; then
printf '[INFO] HF account switched: %s -> %s\n' "$current_username" "$new_username"
elif [[ -n "$new_username" ]]; then
printf '[INFO] HF login successful as: %s\n' "$new_username"
else
printf '[INFO] HF login successful\n'
fi
return 0
else
printf '[ERROR] HF login failed: %s\n' "$login_output" >&2
continue
fi
else
return 1
fi
done
}
# ---- 检测 uv 并设置 Python 命令 ----
if command -v uv &>/dev/null && [[ -f "$REPO_ROOT/pyproject.toml" ]]; then
PYTHON="uv run python3"
else
PYTHON="python3"
fi
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
info() { printf "${GREEN}%s${NC}\n" "$*"; }
warn() { printf "${YELLOW}%s${NC}\n" "$*" >&2; }
error() { printf "${RED}%s${NC}\n" "$*" >&2; }
title() { printf "\n${CYAN}${BOLD}%s${NC}\n" "$*"; }
# ---- List datasets owned by the current HF user ----
# Echoes dataset IDs (one per line) on stdout. Empty stdout = no datasets
# (or hf CLI not logged in).
list_user_datasets() {
local user
user="$(get_default_hf_user 2>/dev/null)" || return 1
hf datasets list --author "$user" 2>/dev/null \
| grep -E '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+[[:space:]]' \
| awk '{print $1}' \
| sort
}
# ---- Generic numbered picker ----
# Args:
# $1 = prompt text (shown above the list)
# $2 = cancel label (e.g. "取消")
# $3 = "yes" / "no" — whether to add a manual-input option
# $@ = items to choose from (each becomes one row)
# Behavior:
# - All UI text goes to stderr; the chosen item is on stdout.
# - Re-prompts on invalid input; non-numeric / out-of-range → warn.
# - Returns 0 + item on stdout on success, 1 on cancel, 2 on non-TTY.
pick_from_list() {
local prompt="$1"
local cancel_label="$2"
local allow_manual="$3"
shift 3
local -a items=("$@")
if [[ ! -t 0 ]]; then
error "Picker requires a TTY"
return 2
fi
if [[ ${#items[@]} -eq 0 ]]; then
warn "没有可选项"
return 1
fi
echo "$prompt:" >&2
local i
for i in "${!items[@]}"; do
printf " %d) %s\n" "$((i+1))" "${items[$i]}" >&2
done
printf " c) %s\n" "$cancel_label" >&2
if [[ "$allow_manual" == "yes" ]]; then
printf " m) 手动输入 ID\n" >&2
fi
while true; do
local max="${#items[@]}"
local prompt_suffix="1-$max, c"
[[ "$allow_manual" == "yes" ]] && prompt_suffix="$prompt_suffix, m"
read -r -p "请选择 [$prompt_suffix]: " choice
case "$choice" in
c|"") return 1 ;;
m)
if [[ "$allow_manual" == "yes" ]]; then
read -r -p "请输入 ID: " manual
if [[ -n "$manual" ]]; then
printf '%s' "$manual"
return 0
fi
warn "输入不能为空"
else
warn "无效选择"
fi
;;
*)
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= max )); then
printf '%s' "${items[$((choice-1))]}"
return 0
fi
warn "无效选择"
;;
esac
done
}
# ---- Pick a dataset (current user's datasets, with manual fallback) ----
# Falls back to manual entry if the user has no datasets (or hf CLI
# isn't logged in).
pick_dataset() {
local prompt="${1:-选择 dataset}"
local -a list
mapfile -t list < <(list_user_datasets)
if [[ ${#list[@]} -eq 0 || -z "${list[0]:-}" ]]; then
warn "账号下没有 dataset(或 hf CLI 未登录),请手动输入"
read -r -p "Dataset ID (user/name): " manual
if [[ -n "$manual" ]]; then
printf '%s' "$manual"
return 0
fi
return 1
fi
pick_from_list "$prompt" "取消" "yes" "${list[@]}"
}
# ============================================================
# 命令实现
# ============================================================
cmd_bootstrap() {
info "▶ 启动交互式部署引导..."
exec "$SCRIPT_DIR/bootstrap-hf.sh"
}
cmd_rebuild() {
local default_user default_repo
default_user="$(get_default_hf_user)" || default_user="<your-hf-username>"
default_repo="$(get_default_repo_name)" || default_repo="<space-name>"
if [ $# -gt 0 ]; then
exec "$SCRIPT_DIR/rebuild-space.sh" "$@"
fi
read -r -p "Space ID (例如 ${default_user}/${default_repo}): " repo
read -r -p "HF_TOKEN (留空从文件读取): " token
exec "$SCRIPT_DIR/rebuild-space.sh" "${repo}" ${token:+"$token"}
}
cmd_cleanup() {
local default_user default_repo
default_user="$(get_default_hf_user)" || default_user="<your-hf-username>"
default_repo="$(get_default_repo_name)" || default_repo="<dataset-name>"
if [ $# -gt 1 ]; then
exec "$SCRIPT_DIR/delete-backups.sh" "$@"
fi
local repo
repo=$(pick_dataset "选择 dataset (提示示例: ${default_user}/${default_repo}-backup)") || return 1
read -r -p "删除此日期及更早的备份 (YYYYMMDD): " date
read -r -p "HF_TOKEN (留空从文件读取): " token
exec "$SCRIPT_DIR/delete-backups.sh" "${repo}" "${date}" ${token:+"$token"}
}
cmd_find_backup() {
local default_user default_repo
default_user="$(get_default_hf_user)" || default_user="<your-hf-username>"
default_repo="$(get_default_repo_name)" || default_repo="<dataset-name>"
if [ $# -gt 0 ]; then
exec $PYTHON "$SCRIPT_DIR/find-largest-backup.py" "$@"
fi
local repo
repo=$(pick_dataset "选择 dataset (提示示例: ${default_user}/${default_repo}-backup)") || return 1
shift_args=()
read -r -p "详细模式? (y/n, 默认 n): " verbose
[[ "$verbose" == "y" ]] && shift_args+=("--verbose")
read -r -p "找到最佳备份后清理旧备份? (y/n, 默认 n): " del
[[ "$del" == "y" ]] && shift_args+=("--delete-before")
if [[ "${del}" == "y" ]]; then
read -r -p "清理后跳过超级压缩(真正释放存储空间)? (y/n, 默认 n): " nosquash
[[ "$nosquash" == "y" ]] && shift_args+=("--no-super-squash")
fi
exec $PYTHON "$SCRIPT_DIR/find-largest-backup.py" "$repo" "${shift_args[@]}"
}
cmd_delete_hf() {
while true; do
echo "删除类型:"
echo " 1) 删除整个仓库 (Space/Dataset/Model)"
echo " 2) 按通配符批量删除文件"
echo " 3) 删除 Space 持久存储"
echo " 9) 返回主菜单"
read -r -p "请选择 [1-3, 9]: " t
echo ""
case "$t" in
1)
read -r -p "Repo ID: " rid
read -r -p "类型 (space/dataset/model): " rtype
"$SCRIPT_DIR/delete-hf.py" repo "$rid" --type "$rtype"
echo ""; echo "按回车继续..."; read -r ;;
2)
read -r -p "Repo ID: " rid
read -r -p "通配符模式: " pat
"$SCRIPT_DIR/delete-hf.py" files "$rid" --pattern "$pat"
echo ""; echo "按回车继续..."; read -r ;;
3)
read -r -p "Space ID: " sid
"$SCRIPT_DIR/delete-hf.py" storage "$sid"
echo ""; echo "按回车继续..."; read -r ;;
9) break ;;
*) error "无效选择" ;;
esac
done
}
cmd_hf_backup() {
local default_user
default_user="$(get_default_hf_user)" || default_user="<your-hf-username>"
while true; do
echo ""
echo "HF Dataset 备份工具"
echo "===================="
echo " 1) backup 创建增量备份"
echo " 2) restore 从本地恢复到 HF"
echo " 3) verify 验证备份完整性"
echo " 4) prune 清理旧快照(释放空间)"
echo " 5) snapshots 查看快照列表"
echo " 6) info 查看 HF Dataset 信息"
echo " 9) 返回主菜单"
read -r -p "请选择 [1-6, 9]: " op
echo ""
case "$op" in
1)
rid=$(pick_dataset "选择 dataset (提示示例: ${default_user}/<dataset>-backup)") || continue
"$SCRIPT_DIR/hf-dataset-mgr.sh" backup "$rid"
echo ""; echo "按回车继续..."; read -r ;;
2)
rid=$(pick_dataset "选择 dataset (提示示例: ${default_user}/<dataset>-backup)") || continue
read -r -p "指定快照名? (留空使用最新): " snap
if [[ -n "$snap" ]]; then
"$SCRIPT_DIR/hf-dataset-mgr.sh" restore "$rid" --snap "$snap"
else
"$SCRIPT_DIR/hf-dataset-mgr.sh" restore "$rid"
fi
echo ""; echo "按回车继续..."; read -r ;;
3)
rid=$(pick_dataset "选择 dataset (提示示例: ${default_user}/<dataset>-backup)") || continue
"$SCRIPT_DIR/hf-dataset-mgr.sh" verify "$rid"
echo ""; echo "按回车继续..."; read -r ;;
4)
rid=$(pick_dataset "选择 dataset (提示示例: ${default_user}/<dataset>-backup)") || continue
read -r -p "保留快照数 (默认 5): " keep
read -r -p "清理后执行 Super-Squash? (y/n, 默认 y): " squash
[[ "$squash" != "n" ]] && SQUASH="--squash" || SQUASH=""
"$SCRIPT_DIR/hf-dataset-mgr.sh" prune "$rid" --keep "${keep:-5}" $SQUASH
echo ""; echo "按回车继续..."; read -r ;;
5)
rid=$(pick_dataset "选择 dataset (提示示例: ${default_user}/<dataset>-backup)") || continue
"$SCRIPT_DIR/hf-dataset-mgr.sh" snapshots "$rid"
echo ""; echo "按回车继续..."; read -r ;;
6)
rid=$(pick_dataset "选择 dataset (提示示例: ${default_user}/<dataset>-backup)") || continue
"$SCRIPT_DIR/hf-dataset-mgr.sh" info "$rid"
echo ""; echo "按回车继续..."; read -r ;;
9) break ;;
*) error "无效选择" ;;
esac
done
}
cmd_hf_account() {
while true; do
echo ""
echo "HF 账号管理工具"
echo "================"
echo " 1) add 添加/更新账号"
echo " 2) list 列出所有账号"
echo " 3) use 切换默认账号"
echo " 4) current 显示当前账号"
echo " 5) remove 删除账号"
echo " 6) get-token 获取账号 token"
echo " 9) 返回主菜单"
read -r -p "请选择 [1-6, 9]: " op
echo ""
case "$op" in
1)
read -r -p "用户名: " name
[[ -z "$name" ]] && { error "用户名不能为空"; continue; }
read -r -p "Token (留空交互式输入): " token
if [[ -z "$token" ]]; then
"$SCRIPT_DIR/hf-account.sh" add "$name"
else
"$SCRIPT_DIR/hf-account.sh" add "$name" "$token"
fi
echo ""; echo "按回车继续..."; read -r ;;
2) "$SCRIPT_DIR/hf-account.sh" list; echo ""; echo "按回车继续..."; read -r ;;
3)
"$SCRIPT_DIR/hf-account.sh" use
echo ""; echo "按回车继续..."; read -r ;;
4) "$SCRIPT_DIR/hf-account.sh" current; echo ""; echo "按回车继续..."; read -r ;;
5)
read -r -p "用户名: " name
[[ -z "$name" ]] && { error "用户名不能为空"; continue; }
"$SCRIPT_DIR/hf-account.sh" remove "$name"
echo ""; echo "按回车继续..."; read -r ;;
6)
read -r -p "用户名 (留空当前账号): " name
if [[ -z "$name" ]]; then
"$SCRIPT_DIR/hf-account.sh" get-token
else
"$SCRIPT_DIR/hf-account.sh" get-token "$name"
fi
echo ""; echo "按回车继续..."; read -r ;;
9) break ;;
*) error "无效选择" ;;
esac
done
}
cmd_storage() {
while true; do
echo ""
echo "存储操作"
echo "========="
echo " 1) 上传文件"
echo " 2) 上传目录"
echo " 3) 下载文件"
echo " 4) 列出文件"
echo " 5) 同步目录"
echo " 9) 返回主菜单"
read -r -p "请选择 [1-5, 9]: " op
echo ""
case "$op" in
1)
read -r -p "文件路径: " f
read -r -p "目标路径 (留空同上): " d
"$SCRIPT_DIR/hf-storage.sh" upload "$f" ${d:+"$d"}
echo ""; echo "按回车继续..."; read -r ;;
2)
read -r -p "目录路径: " d
read -r -p "前缀 (留空无前缀): " p
"$SCRIPT_DIR/hf-storage.sh" upload-dir "$d" ${p:+"$p"}
echo ""; echo "按回车继续..."; read -r ;;
3)
read -r -p "远程文件: " f
read -r -p "本地路径 (留空当前目录): " l
"$SCRIPT_DIR/hf-storage.sh" download "$f" ${l:+"$l"}
echo ""; echo "按回车继续..."; read -r ;;
4) "$SCRIPT_DIR/hf-storage.sh" list; echo ""; echo "按回车继续..."; read -r ;;
5)
read -r -p "本地目录: " l
read -r -p "远程前缀 (留空无): " r
"$SCRIPT_DIR/hf-storage.sh" sync "$l" ${r:+"$r"}
echo ""; echo "按回车继续..."; read -r ;;
9) break ;;
*) error "无效选择" ;;
esac
done
}
cmd_restart_space() {
local default_user default_repo
default_user="$(get_default_hf_user)" || default_user="<your-hf-username>"
default_repo="$(get_default_repo_name)" || default_repo="<space-name>"
local repo="$1"
if [ -z "$repo" ]; then
read -r -p "Space ID (例如 ${default_user}/${default_repo}): " repo
fi
info "▶ 重启 Space: $repo"
if hf spaces restart "$repo" 2>&1; then
info "✓ 重启请求已发送"
else
error "✗ 重启失败"
return 1
fi
}
cmd_pause_space() {
local default_user default_repo
default_user="$(get_default_hf_user)" || default_user="<your-hf-username>"
default_repo="$(get_default_repo_name)" || default_repo="<space-name>"
local repo="$1"
if [ -z "$repo" ]; then
read -r -p "Space ID (例如 ${default_user}/${default_repo}): " repo
fi
warn "▶ 暂停 Space: $repo"
warn "警告: Space 暂停后无法通过网络访问,需手动重启恢复"
read -r -p "确认暂停? (y/n): " confirm
[[ "$confirm" != "y" ]] && info "已取消" && return 0
if hf spaces pause "$repo" 2>&1; then
info "✓ Space 已暂停"
else
error "✗ 暂停失败"
return 1
fi
}
cmd_factory_rebuild() {
local default_user default_repo
default_user="$(get_default_hf_user)" || default_user="<your-hf-username>"
default_repo="$(get_default_repo_name)" || default_repo="<space-name>"
local repo="$1"
if [ -z "$repo" ]; then
read -r -p "Space ID (例如 ${default_user}/${default_repo}): " repo
fi
warn "▶ Factory 重建 Space: $repo"
warn "警告: 这将删除 Space 后重新创建!所有持久存储数据将被清除!"
read -r -p "确认重建? (输入 Space 名称确认): " confirm
[[ "$confirm" != "${repo#*/}" ]] && error "名称不匹配,已取消" && return 1
info "1/3 删除 Space..."
$PYTHON -c "
from huggingface_hub import HfApi
import os
api = HfApi()
try:
api.delete_repo('${repo}', repo_type='space')
print('✓ 已删除')
except Exception as e:
print(f'删除失败(可能不存在): {e}')
"
sleep 3
info "2/3 重新创建 Space..."
hf repos create "$repo" --repo-type space --space-sdk docker --private
info "3/3 上传代码..."
exec "$SCRIPT_DIR/rebuild-space.sh" "$repo"
}
show_menu() {
clear 2>/dev/null || true
title "╔══════════════════════════════════════╗"
title "║ OpenClaw 工具箱 ║"
title "║ 本地开发工具集 ║"
title "╚══════════════════════════════════════╝"
echo ""
info " 1) 🚀 部署到 HF Space"
echo " 交互式全流程部署 OpenClaw 到 Hugging Face Space"
echo ""
info " 2) 📤 推送代码到 Space"
echo " 将本地最新代码强制推送到 Space 并触发重建"
echo ""
info " 3) 🗑️ 清理备份"
echo " 删除 Dataset 中指定日期及更早的所有备份文件"
echo ""
info " 4) 🔍 查找最佳备份"
echo " 综合评分找出最佳备份,可选清理旧备份"
echo ""
info " 5) 💾 HF Dataset 备份"
echo " 本地↔远程交互备份,支持快照/验证/清理/恢复"
echo ""
info " 6) 👤 HF 账号管理"
echo " 多账号切换,添加/删除/切换 HF 用户"
echo ""
info " 7) ❌ 删除 HF 资源"
echo " 删除仓库、批量删除文件、或删除 Space 持久存储"
echo ""
info " 8) 📦 存储管理"
echo " HuggingFace 存储工具:上传/下载/同步文件"
echo ""
info " ── Space 运维 ──"
echo ""
info " 9) 🔄 重启 Space"
echo " 发送重启请求到 HuggingFace Space"
echo ""
info " 10) ⏸️ 暂停 Space"
echo " 暂停 Space,暂停后无法网络访问"
echo ""
info " 11) 🏭 Factory 重建"
echo " 删除 Space 后重新创建并上传代码(清除所有数据和持久存储)"
echo ""
info " 0) ❎ 退出"
echo ""
}
# ============================================================
# 交互菜单
# ============================================================
interactive_mode() {
while true; do
show_menu
read -r -p "请选择 [0-11]: " choice
echo ""
case "$choice" in
1) cmd_bootstrap ;;
2) cmd_rebuild ;;
3) cmd_cleanup ;;
4) cmd_find_backup ;;
5) cmd_hf_backup ;;
6) cmd_hf_account ;;
7) cmd_delete_hf ;;
8) cmd_storage ;;
9) cmd_restart_space ;;
10) cmd_pause_space ;;
11) cmd_factory_rebuild ;;
0) info "再见!"; exit 0 ;;
*) warn "无效选择,请重新输入"; sleep 1 ;;
esac
done
}
show_usage() {
title "OpenClaw 工具箱 — 本地开发工具集"
echo ""
echo "用法: $0 [命令] [参数...]"
echo ""
echo "不带参数时启动交互式菜单。"
echo ""
info "命令模式:"
echo " $0 bootstrap 交互式部署到 HF Space"
echo " $0 rebuild-space <ID> [TOKEN] 推送代码到 Space"
echo " $0 restart-space <ID> 重启 Space"
echo " $0 pause-space <ID> 暂停 Space"
echo " $0 factory-rebuild <ID> Factory 重建 Space(删除重建)"
echo " $0 cleanup <ID> <日期> [TOKEN] 清理旧备份"
echo " $0 find-backup <ID> [选项] 查找最佳备份"
echo " $0 hf-backup <命令> [选项] HF Dataset 本地↔远程交互备份"
echo " $0 hf-account <命令> [选项] HF 多账号管理"
echo " $0 rm-hf <子命令> [参数] 删除 HF 资源"
echo " $0 storage <子命令> [参数] 存储管理"
echo ""
info "hf-backup 子命令:"
echo " $0 hf-backup backup <dataset> 创建增量备份"
echo " $0 hf-backup restore <dataset> 从本地恢复到 HF"
echo " $0 hf-backup verify <dataset> 验证备份完整性"
echo " $0 hf-backup prune <dataset> 清理旧快照"
echo " $0 hf-backup snapshots <dataset> 查看快照列表"
echo " $0 hf-backup info <dataset> 查看 Dataset 信息"
echo ""
info "hf-account 子命令:"
echo " $0 hf-account add <user> [token] 添加/更新账号"
echo " $0 hf-account list 列出所有账号"
echo " $0 hf-account use [user] 切换默认账号(无参:交互式选择器)"
echo " $0 hf-account current 显示当前账号"
echo " $0 hf-account remove <user> 删除账号"
echo " $0 hf-account get-token [user] 获取 token"
echo ""
info "示例:"
echo " $0 # 交互菜单"
echo " $0 rebuild-space <user>/<space> # 推送代码"
echo " $0 hf-backup backup <user>/<dataset>-backup # 备份 dataset"
echo " $0 hf-account add <user> <token> # 添加 HF 账号"
}
# ============================================================
# 主入口
# ============================================================
main() {
# 检查并获取 HF token(支持账号切换)
if [[ -t 0 ]]; then
prompt_hf_account_switch || true
fi
if [ $# -eq 0 ]; then
interactive_mode
exit 0
fi
local cmd="$1"
shift
case "$cmd" in
-h|--help|help) show_usage ;;
bootstrap) cmd_bootstrap "$@" ;;
rebuild-space|rebuild) cmd_rebuild "$@" ;;
restart-space) cmd_restart_space "$@" ;;
pause-space) cmd_pause_space "$@" ;;
factory-rebuild) cmd_factory_rebuild "$@" ;;
cleanup) cmd_cleanup "$@" ;;
find-backup|find) cmd_find_backup "$@" ;;
hf-backup|hfb) cmd_hf_backup "$@" ;;
hf-account|hfa) cmd_hf_account "$@" ;;
rm-hf|delete-hf) cmd_delete_hf "$@" ;;
storage) cmd_storage "$@" ;;
*)
error "未知命令: $cmd"
echo "使用 '$0 --help' 查看可用命令"
exit 1
;;
esac
}
main "$@"