Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
/.config.ini
|
||||||
|
/*.jar
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
: <<'DOC'
|
||||||
|
ask — AI terminal assistant (macOS)
|
||||||
|
|
||||||
|
Setup API key (Keychain):
|
||||||
|
security add-generic-password -a "$USER" -s TK_AI_API_TOKEN -w "YOUR_API_KEY"
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ask "question"
|
||||||
|
ask -i # interactive mode
|
||||||
|
ask -v # verbose (debug raw response)
|
||||||
|
DOC
|
||||||
|
|
||||||
|
# ===== CONFIG =====
|
||||||
|
SERVER="https://ai.acbpro.com/v1/chat/completions"
|
||||||
|
MODEL="gpt-5.3-codex"
|
||||||
|
KEY_NAME="TK_AI_API_TOKEN"
|
||||||
|
|
||||||
|
VERBOSE=0
|
||||||
|
INTERACTIVE=0
|
||||||
|
|
||||||
|
# ===== COLORS =====
|
||||||
|
GREEN="\033[1;32m"
|
||||||
|
CYAN="\033[1;36m"
|
||||||
|
YELLOW="\033[1;33m"
|
||||||
|
RED="\033[1;31m"
|
||||||
|
RESET="\033[0m"
|
||||||
|
|
||||||
|
# ===== PARSE FLAGS =====
|
||||||
|
while [[ "$1" == -* ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-v) VERBOSE=1 ;;
|
||||||
|
-i) INTERACTIVE=1 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# ===== LOAD TOKEN =====
|
||||||
|
API_TOKEN=$(security find-generic-password -a "$USER" -s "$KEY_NAME" -w 2>/dev/null | tr -d '\n')
|
||||||
|
|
||||||
|
if [ -z "$API_TOKEN" ]; then
|
||||||
|
echo "❌ API token not found in Keychain ($KEY_NAME)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== SYSTEM PROMPT =====
|
||||||
|
SYSTEM_PROMPT="You are a terminal assistant for macOS (zsh/bash).
|
||||||
|
|
||||||
|
- Default: return ONLY the command, no explanation.
|
||||||
|
- If user asks for explanation, then explain.
|
||||||
|
- Commands must be safe and runnable.
|
||||||
|
- Prefer standard macOS/Linux tools.
|
||||||
|
- No markdown, no code block.
|
||||||
|
"
|
||||||
|
|
||||||
|
# ===== TERMINAL STATUS =====
|
||||||
|
supports_status_line() {
|
||||||
|
[ -t 2 ] && [ "${TERM:-}" != "dumb" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
render_status_line() {
|
||||||
|
supports_status_line || return 0
|
||||||
|
printf '\r\033[2K%b' "$1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_status_line() {
|
||||||
|
supports_status_line || return 0
|
||||||
|
printf '\r\033[2K' >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
print_response() {
|
||||||
|
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then
|
||||||
|
echo -e "${CYAN}🤖 $1${RESET}"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== FUNCTION CALL API =====
|
||||||
|
call_api() {
|
||||||
|
local Q="$1"
|
||||||
|
local CWD GIT_BRANCH LAST_CMD JSON
|
||||||
|
local START_TIME TMP_FILE CURL_PID
|
||||||
|
local SPINNER i NOW ELAPSED CHAR COLOR
|
||||||
|
local RESPONSE CONTENT CURL_STATUS
|
||||||
|
|
||||||
|
CWD="$(pwd)"
|
||||||
|
GIT_BRANCH="$(git branch --show-current 2>/dev/null)"
|
||||||
|
LAST_CMD="$(fc -ln -1 2>/dev/null)"
|
||||||
|
|
||||||
|
JSON=$(jq -n \
|
||||||
|
--arg model "$MODEL" \
|
||||||
|
--arg sys "$SYSTEM_PROMPT" \
|
||||||
|
--arg q "$Q" \
|
||||||
|
--arg cwd "$CWD" \
|
||||||
|
--arg branch "$GIT_BRANCH" \
|
||||||
|
--arg last "$LAST_CMD" \
|
||||||
|
'{
|
||||||
|
model: $model,
|
||||||
|
messages: [
|
||||||
|
{role: "system", content: $sys},
|
||||||
|
{role: "user", content:
|
||||||
|
("Context:
|
||||||
|
" +
|
||||||
|
"cwd: " + $cwd + "
|
||||||
|
" +
|
||||||
|
"git_branch: " + $branch + "
|
||||||
|
" +
|
||||||
|
"last_command: " + $last + "
|
||||||
|
|
||||||
|
" +
|
||||||
|
"Request:
|
||||||
|
" + $q)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ===== CALL (async) =====
|
||||||
|
START_TIME=$(date +%s)
|
||||||
|
TMP_FILE=$(mktemp)
|
||||||
|
|
||||||
|
curl -s "$SERVER" \
|
||||||
|
-H "Authorization: Bearer $API_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$JSON" > "$TMP_FILE" &
|
||||||
|
|
||||||
|
CURL_PID=$!
|
||||||
|
trap 'clear_status_line; [ -n "${CURL_PID:-}" ] && kill "${CURL_PID}" 2>/dev/null; [ -n "${TMP_FILE:-}" ] && rm -f "${TMP_FILE}"; exit 130' INT TERM
|
||||||
|
|
||||||
|
# ===== SPINNER + TIMER =====
|
||||||
|
SPINNER='|/-\'
|
||||||
|
i=0
|
||||||
|
if supports_status_line; then
|
||||||
|
while kill -0 "$CURL_PID" 2>/dev/null; do
|
||||||
|
NOW=$(date +%s)
|
||||||
|
ELAPSED=$((NOW - START_TIME))
|
||||||
|
|
||||||
|
CHAR=${SPINNER:i%${#SPINNER}:1}
|
||||||
|
|
||||||
|
if [ "$ELAPSED" -lt 10 ]; then COLOR="$GREEN";
|
||||||
|
elif [ "$ELAPSED" -lt 30 ]; then COLOR="$YELLOW";
|
||||||
|
else COLOR="$RED"; fi
|
||||||
|
|
||||||
|
render_status_line "${COLOR}⏳ calling ${CHAR} ${ELAPSED}s${RESET}"
|
||||||
|
|
||||||
|
i=$((i+1))
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait "$CURL_PID"
|
||||||
|
CURL_STATUS=$?
|
||||||
|
clear_status_line
|
||||||
|
trap - INT TERM
|
||||||
|
|
||||||
|
RESPONSE=$(<"$TMP_FILE")
|
||||||
|
rm -f "$TMP_FILE"
|
||||||
|
|
||||||
|
if [ "$CURL_STATUS" -ne 0 ]; then
|
||||||
|
echo "❌ request failed (curl exit $CURL_STATUS)" >&2
|
||||||
|
[ -n "$RESPONSE" ] && echo "$RESPONSE" >&2
|
||||||
|
return "$CURL_STATUS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$VERBOSE" -eq 1 ]; then
|
||||||
|
echo -e "${YELLOW}🔧 RAW RESPONSE:${RESET}" >&2
|
||||||
|
echo "$RESPONSE" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONTENT=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // .response // .text // .')
|
||||||
|
|
||||||
|
print_response "$CONTENT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== INTERACTIVE MODE =====
|
||||||
|
if [ "$INTERACTIVE" -eq 1 ]; then
|
||||||
|
echo -e "${GREEN}💬 Interactive mode (Ctrl+C để thoát)${RESET}"
|
||||||
|
while true; do
|
||||||
|
printf "${GREEN}❓ ask> ${RESET}"
|
||||||
|
read Q
|
||||||
|
[ -z "$Q" ] && continue
|
||||||
|
call_api "$Q"
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== NORMAL MODE =====
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Usage: ask [-v] [-i] <question>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
call_api "$*"
|
||||||
Executable
+859
@@ -0,0 +1,859 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# cleanup.sh
|
||||||
|
# Script dọn dẹp các ứng dụng nặng và các bản cũ trên macOS
|
||||||
|
# Sử dụng: bash cleanup.sh
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: cleanup chung cho thư mục chứa version
|
||||||
|
# $1 = label (vd: "Edge", "Edge Updater")
|
||||||
|
# $2 = đường dẫn thư mục
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_versions() {
|
||||||
|
local label="$1"
|
||||||
|
local target_dir="$2"
|
||||||
|
|
||||||
|
echo "--- [$label] ---"
|
||||||
|
echo " $target_dir"
|
||||||
|
|
||||||
|
if [ ! -d "$target_dir" ]; then
|
||||||
|
echo " ⚠ Thư mục không tồn tại."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd "$target_dir" > /dev/null || return
|
||||||
|
|
||||||
|
# Lấy các thư mục version dạng x.y.z.w và sort theo version số.
|
||||||
|
local raw_dirs=()
|
||||||
|
local d
|
||||||
|
shopt -s nullglob
|
||||||
|
for d in [0-9]*.[0-9]*.[0-9]*.[0-9]*; do
|
||||||
|
[[ -d "$d" ]] && raw_dirs+=("$d")
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if [ ${#raw_dirs[@]} -eq 0 ]; then
|
||||||
|
echo " ⚠ Thư mục rỗng, không có phiên bản nào."
|
||||||
|
popd > /dev/null
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local dirs=()
|
||||||
|
while IFS= read -r d; do
|
||||||
|
[[ -n "$d" ]] && dirs+=("$d")
|
||||||
|
done < <(
|
||||||
|
printf '%s\n' "${raw_dirs[@]}" |
|
||||||
|
awk -F'.' '{ printf "%05d.%05d.%05d.%05d\t%s\n", $1, $2, $3, $4, $0 }' |
|
||||||
|
sort |
|
||||||
|
cut -f2-
|
||||||
|
)
|
||||||
|
|
||||||
|
local last_index=$(( ${#dirs[@]} - 1 ))
|
||||||
|
local keep="${dirs[$last_index]}"
|
||||||
|
|
||||||
|
# Helper: lấy size thư mục
|
||||||
|
dir_size() { du -sh "$1" 2>/dev/null | cut -f1 | xargs; }
|
||||||
|
|
||||||
|
if [ ${#dirs[@]} -le 1 ]; then
|
||||||
|
echo " ✓ Phiên bản hiện tại: $keep ($(dir_size "$keep")) — không có bản cũ."
|
||||||
|
popd > /dev/null
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ✓ Giữ lại: $keep ($(dir_size "$keep"))"
|
||||||
|
echo " ✗ Sẽ xóa:"
|
||||||
|
local i
|
||||||
|
for ((i = 0; i < ${#dirs[@]} - 1; i++)); do
|
||||||
|
echo " - ${dirs[$i]} ($(dir_size "${dirs[$i]}"))"
|
||||||
|
done
|
||||||
|
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
popd > /dev/null
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for ((i = 0; i < ${#dirs[@]} - 1; i++)); do
|
||||||
|
echo " Đã xóa: ${dirs[$i]}"
|
||||||
|
rm -rf -- "${dirs[$i]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
popd > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup Go mod cache
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_go() {
|
||||||
|
echo "--- [Go Mod Cache] ---"
|
||||||
|
if ! command -v go &> /dev/null; then
|
||||||
|
echo " ⚠ go command not found."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local GOMODCACHE
|
||||||
|
GOMODCACHE=$(go env GOMODCACHE)
|
||||||
|
|
||||||
|
if [ -d "$GOMODCACHE" ]; then
|
||||||
|
echo " $GOMODCACHE"
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$GOMODCACHE" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " ✗ Sẽ xóa bộ nhớ cache: $size"
|
||||||
|
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
go clean -modcache
|
||||||
|
echo " Đã xong."
|
||||||
|
else
|
||||||
|
echo " ✓ Bộ nhớ cache rỗng hoặc không tồn tại."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup NPM cache
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_npm() {
|
||||||
|
echo "--- [NPM Cache] ---"
|
||||||
|
if ! command -v npm &> /dev/null; then
|
||||||
|
echo " ⚠ npm command not found."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local NPM_CACHE
|
||||||
|
NPM_CACHE=$(npm config get cache)
|
||||||
|
|
||||||
|
if [ -d "$NPM_CACHE" ]; then
|
||||||
|
echo " $NPM_CACHE"
|
||||||
|
local size
|
||||||
|
# Lưu ý: npm cache size thực tế thường lớn hơn kết quả du vì cấu trúc thư mục phức tạp
|
||||||
|
size=$(du -sh "$NPM_CACHE" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " ✗ Sẽ xóa bộ nhớ cache: $size"
|
||||||
|
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
npm cache clean --force
|
||||||
|
echo " Đã xong."
|
||||||
|
else
|
||||||
|
echo " ✓ Bộ nhớ cache rỗng hoặc không tồn tại."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup old VS Code extensions
|
||||||
|
# Giữ lại phiên bản mới nhất của mỗi extension
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_vscode_extensions() {
|
||||||
|
echo "--- [VS Code Extensions] ---"
|
||||||
|
local EXT_DIR="$HOME/.vscode/extensions"
|
||||||
|
echo " $EXT_DIR"
|
||||||
|
|
||||||
|
if [ ! -d "$EXT_DIR" ]; then
|
||||||
|
echo " ⚠ Thư mục không tồn tại."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build "ext_id<TAB>dirname" pairs
|
||||||
|
local entries=()
|
||||||
|
local basename ext_id
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
for dir in "$EXT_DIR"/*/; do
|
||||||
|
basename=$(command basename "$dir")
|
||||||
|
[[ "$basename" == .* ]] && continue
|
||||||
|
# Extract extension ID: everything before -<version>
|
||||||
|
ext_id=$(echo "$basename" | sed 's/-[0-9][0-9.]*.*$//')
|
||||||
|
[ -z "$ext_id" ] && continue
|
||||||
|
entries+=("${ext_id}"$'\t'"${basename}")
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if [ ${#entries[@]} -eq 0 ]; then
|
||||||
|
echo " ⚠ Không có extension nào."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sort by ext_id (field 1), then by dirname (field 2) for version ordering
|
||||||
|
local sorted
|
||||||
|
sorted=$(printf '%s\n' "${entries[@]}" | sort -t$'\t' -k1,1 -k2,2)
|
||||||
|
|
||||||
|
# Group by ext_id, find old versions
|
||||||
|
local to_delete=()
|
||||||
|
local to_keep=()
|
||||||
|
local group=()
|
||||||
|
local prev_id=""
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r ext_id dirname; do
|
||||||
|
if [ -n "$prev_id" ] && [ "$ext_id" != "$prev_id" ]; then
|
||||||
|
# Process previous group
|
||||||
|
if [ ${#group[@]} -gt 1 ]; then
|
||||||
|
local last=$(( ${#group[@]} - 1 ))
|
||||||
|
to_keep+=("${group[$last]}")
|
||||||
|
local g
|
||||||
|
for ((g = 0; g < last; g++)); do
|
||||||
|
to_delete+=("${group[$g]}")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
group=()
|
||||||
|
fi
|
||||||
|
group+=("$dirname")
|
||||||
|
prev_id="$ext_id"
|
||||||
|
done <<< "$sorted"
|
||||||
|
|
||||||
|
# Process last group
|
||||||
|
if [ ${#group[@]} -gt 1 ]; then
|
||||||
|
local last=$(( ${#group[@]} - 1 ))
|
||||||
|
to_keep+=("${group[$last]}")
|
||||||
|
local g
|
||||||
|
for ((g = 0; g < last; g++)); do
|
||||||
|
to_delete+=("${group[$g]}")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#to_delete[@]} -eq 0 ]; then
|
||||||
|
echo " ✓ Không có extension cũ để xóa."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ✓ Giữ lại (mới nhất):"
|
||||||
|
local k
|
||||||
|
for k in "${to_keep[@]}"; do
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$EXT_DIR/$k" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " + $k ($size)"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo " ✗ Sẽ xóa (cũ):"
|
||||||
|
local d
|
||||||
|
for d in "${to_delete[@]}"; do
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$EXT_DIR/$d" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " - $d ($size)"
|
||||||
|
done
|
||||||
|
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for d in "${to_delete[@]}"; do
|
||||||
|
echo " Đã xóa: $d"
|
||||||
|
rm -rf -- "$EXT_DIR/$d"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup old Antigravity browser recordings
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_agy_recordings() {
|
||||||
|
local days="${1:-7}"
|
||||||
|
local rec_dir="$HOME/.gemini/antigravity/browser_recordings"
|
||||||
|
|
||||||
|
echo "--- [Antigravity Recordings] ---"
|
||||||
|
echo " $rec_dir"
|
||||||
|
|
||||||
|
if [ ! -d "$rec_dir" ]; then
|
||||||
|
echo " ⚠ Thư mục không tồn tại."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local to_delete=()
|
||||||
|
local total_size=0
|
||||||
|
local dir
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
for dir in "$rec_dir"/*/; do
|
||||||
|
[ -d "$dir" ] || continue
|
||||||
|
local dirname
|
||||||
|
dirname=$(basename "$dir")
|
||||||
|
local size mod_date
|
||||||
|
size=$(du -sh "$dir" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
mod_date=$(stat -f "%Sm" -t "%Y-%m-%d" "$dir")
|
||||||
|
|
||||||
|
if [ "$(find "$dir" -maxdepth 0 -mtime +"$days" 2>/dev/null)" ]; then
|
||||||
|
echo " - $dirname ($size, $mod_date)"
|
||||||
|
to_delete+=("$dir")
|
||||||
|
total_size=$((total_size + $(du -sk "$dir" | cut -f1)))
|
||||||
|
else
|
||||||
|
echo " + $dirname ($size, $mod_date) [KEEP]"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if [ ${#to_delete[@]} -eq 0 ]; then
|
||||||
|
echo " ✓ Không có recording cũ (>${days} ngày) để xóa."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local total_human
|
||||||
|
total_human=$(echo "scale=1; $total_size / 1024" | bc)
|
||||||
|
echo " ✗ Sẽ xóa ${#to_delete[@]} recording(s), tổng: ${total_human} MiB"
|
||||||
|
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for dir in "${to_delete[@]}"; do
|
||||||
|
echo " Đã xóa: $(basename "$dir")"
|
||||||
|
rm -rf -- "$dir"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup Homebrew
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_homebrew() {
|
||||||
|
echo "--- [Homebrew] ---"
|
||||||
|
if ! command -v brew &> /dev/null; then
|
||||||
|
echo " ⚠ brew command not found."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Đang cập nhật Homebrew..."
|
||||||
|
brew update --quiet
|
||||||
|
|
||||||
|
local outdated
|
||||||
|
outdated=$(brew outdated 2>/dev/null)
|
||||||
|
if [ -n "$outdated" ]; then
|
||||||
|
echo " ✗ Các package cần upgrade:"
|
||||||
|
echo "$outdated" | sed 's/^/ - /'
|
||||||
|
local confirm
|
||||||
|
read -r -p " Upgrade tất cả? (Y/n): " confirm
|
||||||
|
if [[ -z "$confirm" || "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
brew upgrade --quiet
|
||||||
|
echo " Đã upgrade xong."
|
||||||
|
else
|
||||||
|
echo " Bỏ qua upgrade."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✓ Tất cả package đã cập nhật."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup cache
|
||||||
|
local cache_size
|
||||||
|
cache_size=$(brew --cache 2>/dev/null)
|
||||||
|
if [ -d "$cache_size" ]; then
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$cache_size" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " ✗ Cache: $size ($cache_size)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Đang dọn dẹp cache và autoremove..."
|
||||||
|
brew cleanup --prune=all -s 2>/dev/null
|
||||||
|
brew autoremove 2>/dev/null
|
||||||
|
echo " Đã xong."
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup VS Code C++ Tools cache
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_vscode_cpptools() {
|
||||||
|
local cache_dir="$HOME/Library/Caches/vscode-cpptools"
|
||||||
|
|
||||||
|
echo "--- [VS Code C++ Tools Cache] ---"
|
||||||
|
echo " $cache_dir"
|
||||||
|
|
||||||
|
if [ ! -d "$cache_dir" ]; then
|
||||||
|
echo " ⚠ Thư mục không tồn tại."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local total_size
|
||||||
|
total_size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " Tổng dung lượng: $total_size"
|
||||||
|
|
||||||
|
# ipch (IntelliSense cache) - thường chiếm nhiều nhất
|
||||||
|
if [ -d "$cache_dir/ipch" ]; then
|
||||||
|
local ipch_size
|
||||||
|
ipch_size=$(du -sh "$cache_dir/ipch" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " - ipch (IntelliSense cache): $ipch_size"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Các thư mục hash cache khác
|
||||||
|
local hash_count=0
|
||||||
|
local hash_size=0
|
||||||
|
local d
|
||||||
|
shopt -s nullglob
|
||||||
|
for d in "$cache_dir"/*/; do
|
||||||
|
local dirname
|
||||||
|
dirname=$(basename "$d")
|
||||||
|
[[ "$dirname" == "ipch" ]] && continue
|
||||||
|
hash_count=$((hash_count + 1))
|
||||||
|
hash_size=$((hash_size + $(du -sk "$d" | cut -f1)))
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if [ $hash_count -gt 0 ]; then
|
||||||
|
local hash_human
|
||||||
|
hash_human=$(echo "scale=1; $hash_size / 1024" | bc)
|
||||||
|
echo " - $hash_count thư mục cache khác: ${hash_human} MiB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ✗ Sẽ xóa toàn bộ cache"
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf -- "$cache_dir"/*
|
||||||
|
echo " Đã xóa toàn bộ cache. VS Code sẽ tạo lại khi cần."
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup IDE logs (JetBrains, Google, etc.)
|
||||||
|
# Giữ lại phiên bản mới nhất của mỗi IDE
|
||||||
|
# $1 = label, $2 = đường dẫn thư mục logs
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_ide_logs() {
|
||||||
|
local label="$1"
|
||||||
|
local logs_dir="$2"
|
||||||
|
|
||||||
|
echo "--- [$label Logs] ---"
|
||||||
|
echo " $logs_dir"
|
||||||
|
|
||||||
|
if [ ! -d "$logs_dir" ]; then
|
||||||
|
echo " ⚠ Thư mục không tồn tại."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local to_delete=()
|
||||||
|
local all_dirs=()
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
for d in "$logs_dir"/*/; do
|
||||||
|
local dirname
|
||||||
|
dirname=$(basename "$d")
|
||||||
|
[[ "$dirname" == "Toolbox" || "$dirname" == ".DS_Store" ]] && continue
|
||||||
|
all_dirs+=("$dirname")
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if [ ${#all_dirs[@]} -eq 0 ]; then
|
||||||
|
echo " ✓ Không có log nào."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local sorted
|
||||||
|
sorted=$(printf '%s\n' "${all_dirs[@]}" | sort)
|
||||||
|
|
||||||
|
local prev_ide=""
|
||||||
|
local group=()
|
||||||
|
|
||||||
|
while IFS= read -r dirname; do
|
||||||
|
[ -z "$dirname" ] && continue
|
||||||
|
local ide_name
|
||||||
|
ide_name=$(echo "$dirname" | sed 's/[0-9].*$//')
|
||||||
|
[ -z "$ide_name" ] && continue
|
||||||
|
|
||||||
|
if [ -n "$prev_ide" ] && [ "$ide_name" != "$prev_ide" ]; then
|
||||||
|
if [ ${#group[@]} -gt 0 ]; then
|
||||||
|
local last_index=$(( ${#group[@]} - 1 ))
|
||||||
|
local keep="${group[$last_index]}"
|
||||||
|
local keep_size
|
||||||
|
keep_size=$(du -sh "$logs_dir/$keep" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " [$prev_ide]"
|
||||||
|
echo " + $keep ($keep_size) [KEEP]"
|
||||||
|
local i
|
||||||
|
for ((i = 0; i < last_index; i++)); do
|
||||||
|
local old="${group[$i]}"
|
||||||
|
local old_size
|
||||||
|
old_size=$(du -sh "$logs_dir/$old" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " - $old ($old_size)"
|
||||||
|
to_delete+=("$logs_dir/$old")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
group=()
|
||||||
|
fi
|
||||||
|
group+=("$dirname")
|
||||||
|
prev_ide="$ide_name"
|
||||||
|
done <<< "$sorted"
|
||||||
|
|
||||||
|
# Process the last group
|
||||||
|
if [ ${#group[@]} -gt 0 ]; then
|
||||||
|
local last_index=$(( ${#group[@]} - 1 ))
|
||||||
|
local keep="${group[$last_index]}"
|
||||||
|
local keep_size
|
||||||
|
keep_size=$(du -sh "$logs_dir/$keep" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " [$prev_ide]"
|
||||||
|
echo " + $keep ($keep_size) [KEEP]"
|
||||||
|
local i
|
||||||
|
for ((i = 0; i < last_index; i++)); do
|
||||||
|
local old="${group[$i]}"
|
||||||
|
local old_size
|
||||||
|
old_size=$(du -sh "$logs_dir/$old" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " - $old ($old_size)"
|
||||||
|
to_delete+=("$logs_dir/$old")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#to_delete[@]} -eq 0 ]; then
|
||||||
|
echo " ✓ Không có log cũ để xóa."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa ${#to_delete[@]} thư mục cũ? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for d in "${to_delete[@]}"; do
|
||||||
|
echo " Đã xóa: $(basename "$d")"
|
||||||
|
rm -rf -- "$d"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup pip cache
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_pip() {
|
||||||
|
echo "--- [Pip Cache] ---"
|
||||||
|
if ! command -v pip3 &> /dev/null; then
|
||||||
|
echo " ⚠ pip3 command not found."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cache_dir
|
||||||
|
cache_dir=$(pip3 cache dir 2>/dev/null)
|
||||||
|
if [ -d "$cache_dir" ]; then
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " $cache_dir"
|
||||||
|
echo " ✗ Cache: $size"
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
pip3 cache purge 2>/dev/null
|
||||||
|
echo " Đã xong."
|
||||||
|
else
|
||||||
|
echo " ✓ Cache rỗng."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup deno cache
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_deno() {
|
||||||
|
echo "--- [Deno Cache] ---"
|
||||||
|
local deno_cache="$HOME/Library/Caches/deno"
|
||||||
|
echo " $deno_cache"
|
||||||
|
|
||||||
|
if [ ! -d "$deno_cache" ]; then
|
||||||
|
echo " ⚠ Thư mục không tồn tại."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$deno_cache" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " ✗ Cache: $size"
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
rm -rf -- "$deno_cache"/*
|
||||||
|
echo " Đã xong."
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup go build cache
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_go_build() {
|
||||||
|
echo "--- [Go Build Cache] ---"
|
||||||
|
if ! command -v go &> /dev/null; then
|
||||||
|
echo " ⚠ go command not found."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cache_dir
|
||||||
|
cache_dir=$(go env GOCACHE)
|
||||||
|
if [ -d "$cache_dir" ]; then
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " $cache_dir"
|
||||||
|
echo " ✗ Cache: $size"
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
go clean -cache
|
||||||
|
echo " Đã xong."
|
||||||
|
else
|
||||||
|
echo " ✓ Cache rỗng."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Function: Cleanup generic cache directory
|
||||||
|
# $1 = label, $2 = đường dẫn thư mục
|
||||||
|
# -----------------------------
|
||||||
|
cleanup_cache_dir() {
|
||||||
|
local label="$1"
|
||||||
|
local cache_dir="$2"
|
||||||
|
|
||||||
|
echo "--- [$label Cache] ---"
|
||||||
|
echo " $cache_dir"
|
||||||
|
|
||||||
|
if [ ! -d "$cache_dir" ]; then
|
||||||
|
echo " ⚠ Thư mục không tồn tại."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local size
|
||||||
|
size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1 | xargs)
|
||||||
|
echo " ✗ Dung lượng: $size"
|
||||||
|
local confirm
|
||||||
|
read -r -p " Xác nhận xóa? (Y/n): " confirm
|
||||||
|
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
|
||||||
|
echo " Đã hủy."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
rm -rf -- "$cache_dir"/*
|
||||||
|
echo " Đã xong."
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Interactive Menu System
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Tên các nhóm cleanup
|
||||||
|
MENU_ITEMS=(
|
||||||
|
"All"
|
||||||
|
"Browsers (Edge, Mozilla, Playwright)"
|
||||||
|
"IDE (VS Code, AntiGravity)"
|
||||||
|
"Dev Caches (Go, NPM, Pip, Deno)"
|
||||||
|
"IDE Logs (JetBrains, Google)"
|
||||||
|
"Other Caches (Raspberry Pi)"
|
||||||
|
"Homebrew"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Trạng thái chọn (0=off, 1=on), mặc định All=on
|
||||||
|
MENU_SELECTED=(1 1 1 1 1 1 1)
|
||||||
|
|
||||||
|
MENU_CURSOR=0
|
||||||
|
MENU_COUNT=${#MENU_ITEMS[@]}
|
||||||
|
|
||||||
|
# Vẽ menu lên terminal
|
||||||
|
draw_menu() {
|
||||||
|
# Di chuyển cursor lên đầu menu để vẽ lại
|
||||||
|
if [ "$1" = "redraw" ]; then
|
||||||
|
printf "\033[%dA" "$((MENU_COUNT + 2))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Chọn mục cần dọn dẹp (↑↓: di chuyển | Space: chọn/bỏ chọn | Enter: chạy các mục có [✓]):"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local i
|
||||||
|
for ((i = 0; i < MENU_COUNT; i++)); do
|
||||||
|
local prefix=" "
|
||||||
|
[ $i -eq $MENU_CURSOR ] && prefix="▸ "
|
||||||
|
|
||||||
|
local check="[ ]"
|
||||||
|
[ "${MENU_SELECTED[$i]}" -eq 1 ] && check="[✓]"
|
||||||
|
|
||||||
|
local label="${MENU_ITEMS[$i]}"
|
||||||
|
|
||||||
|
# In dòng, xóa hết ký tự cũ trên dòng
|
||||||
|
printf "\r\033[K%s%s %s\n" "$prefix" "$check" "$label"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Toggle All: chọn hoặc bỏ tất cả
|
||||||
|
toggle_all() {
|
||||||
|
local new_val="${MENU_SELECTED[0]}"
|
||||||
|
# Nếu All đang bật → tắt hết, ngược lại → bật hết
|
||||||
|
if [ "$new_val" -eq 1 ]; then
|
||||||
|
new_val=0
|
||||||
|
else
|
||||||
|
new_val=1
|
||||||
|
fi
|
||||||
|
local i
|
||||||
|
for ((i = 0; i < MENU_COUNT; i++)); do
|
||||||
|
MENU_SELECTED[$i]=$new_val
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cập nhật lại trạng thái All dựa trên các item con
|
||||||
|
sync_all() {
|
||||||
|
local all_on=1
|
||||||
|
local i
|
||||||
|
for ((i = 1; i < MENU_COUNT; i++)); do
|
||||||
|
if [ "${MENU_SELECTED[$i]}" -eq 0 ]; then
|
||||||
|
all_on=0
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
MENU_SELECTED[0]=$all_on
|
||||||
|
}
|
||||||
|
|
||||||
|
# Chạy interactive menu
|
||||||
|
run_menu() {
|
||||||
|
echo ""
|
||||||
|
echo "===== 🧹 Cleanup macOS ====="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
draw_menu
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# Đọc 1 ký tự (không echo, không buffer)
|
||||||
|
IFS= read -rsn1 key
|
||||||
|
|
||||||
|
case "$key" in
|
||||||
|
# ESC sequence (arrow keys)
|
||||||
|
$'\x1b')
|
||||||
|
read -rsn2 seq
|
||||||
|
case "$seq" in
|
||||||
|
'[A') # Up
|
||||||
|
MENU_CURSOR=$(( (MENU_CURSOR - 1 + MENU_COUNT) % MENU_COUNT ))
|
||||||
|
;;
|
||||||
|
'[B') # Down
|
||||||
|
MENU_CURSOR=$(( (MENU_CURSOR + 1) % MENU_COUNT ))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
# Space = toggle
|
||||||
|
' ')
|
||||||
|
if [ $MENU_CURSOR -eq 0 ]; then
|
||||||
|
toggle_all
|
||||||
|
else
|
||||||
|
if [ "${MENU_SELECTED[$MENU_CURSOR]}" -eq 1 ]; then
|
||||||
|
MENU_SELECTED[$MENU_CURSOR]=0
|
||||||
|
else
|
||||||
|
MENU_SELECTED[$MENU_CURSOR]=1
|
||||||
|
fi
|
||||||
|
sync_all
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
# Enter = confirm
|
||||||
|
'')
|
||||||
|
echo ""
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
draw_menu "redraw"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Hàm chạy từng nhóm
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
run_browsers() {
|
||||||
|
cleanup_versions "Edge" "$HOME/Library/Application Support/Microsoft/EdgeUpdater/apps/msedge-stable"
|
||||||
|
echo ""
|
||||||
|
cleanup_versions "Edge Updater" "$HOME/Library/Application Support/Microsoft/EdgeUpdater/apps/msedge-updater"
|
||||||
|
echo ""
|
||||||
|
cleanup_cache_dir "Mozilla" "$HOME/Library/Caches/Mozilla"
|
||||||
|
echo ""
|
||||||
|
cleanup_cache_dir "Playwright" "$HOME/Library/Caches/ms-playwright"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
run_ide() {
|
||||||
|
cleanup_vscode_extensions
|
||||||
|
echo ""
|
||||||
|
cleanup_vscode_cpptools
|
||||||
|
echo ""
|
||||||
|
cleanup_cache_dir "VS Code ShipIt" "$HOME/Library/Caches/com.microsoft.VSCode.ShipIt"
|
||||||
|
echo ""
|
||||||
|
cleanup_agy_recordings
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
run_dev_caches() {
|
||||||
|
cleanup_go
|
||||||
|
echo ""
|
||||||
|
cleanup_go_build
|
||||||
|
echo ""
|
||||||
|
cleanup_npm
|
||||||
|
echo ""
|
||||||
|
cleanup_pip
|
||||||
|
echo ""
|
||||||
|
cleanup_deno
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
run_ide_logs() {
|
||||||
|
cleanup_ide_logs "JetBrains" "$HOME/Library/Logs/JetBrains"
|
||||||
|
echo ""
|
||||||
|
cleanup_ide_logs "Google" "$HOME/Library/Logs/Google"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
run_other_caches() {
|
||||||
|
cleanup_cache_dir "Raspberry Pi" "$HOME/Library/Caches/Raspberry Pi"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
run_homebrew() {
|
||||||
|
cleanup_homebrew
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Main
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Map index → function
|
||||||
|
GROUP_FUNCS=(
|
||||||
|
"" # 0 = All (handled separately)
|
||||||
|
"run_browsers" # 1
|
||||||
|
"run_ide" # 2
|
||||||
|
"run_dev_caches" # 3
|
||||||
|
"run_ide_logs" # 4
|
||||||
|
"run_other_caches" # 5
|
||||||
|
"run_homebrew" # 6
|
||||||
|
)
|
||||||
|
|
||||||
|
run_menu
|
||||||
|
|
||||||
|
# Kiểm tra có mục nào được chọn không
|
||||||
|
has_selection=0
|
||||||
|
for ((i = 1; i < MENU_COUNT; i++)); do
|
||||||
|
if [ "${MENU_SELECTED[$i]}" -eq 1 ]; then
|
||||||
|
has_selection=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$has_selection" -eq 0 ]; then
|
||||||
|
echo "Không có mục nào được chọn. Thoát."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Chạy các nhóm đã chọn
|
||||||
|
echo "===== Bắt đầu cleanup ====="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for ((i = 1; i < MENU_COUNT; i++)); do
|
||||||
|
if [ "${MENU_SELECTED[$i]}" -eq 1 ]; then
|
||||||
|
${GROUP_FUNCS[$i]}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "===== ✅ Cleanup hoàn tất ====="
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# glm-quota — Check Zhipu AI (GLM) API quota
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# glm-quota # show quota
|
||||||
|
# glm-quota -v # show raw JSON response
|
||||||
|
#
|
||||||
|
# Setup API key (Keychain):
|
||||||
|
# security add-generic-password -a "$USER" -s GLM_API_KEY -w "YOUR_API_KEY"
|
||||||
|
|
||||||
|
KEY_NAME="GLM_API_KEY"
|
||||||
|
URL="https://open.bigmodel.cn/api/monitor/usage/quota/limit"
|
||||||
|
|
||||||
|
# ===== COLORS =====
|
||||||
|
GREEN="\033[1;32m"
|
||||||
|
CYAN="\033[1;36m"
|
||||||
|
YELLOW="\033[1;33m"
|
||||||
|
RED="\033[1;31m"
|
||||||
|
DIM="\033[2m"
|
||||||
|
RESET="\033[0m"
|
||||||
|
|
||||||
|
# ===== ARGS =====
|
||||||
|
VERBOSE=0
|
||||||
|
[[ "$1" == "-v" ]] && VERBOSE=1
|
||||||
|
|
||||||
|
# ===== LOAD TOKEN =====
|
||||||
|
API_TOKEN=$(security find-generic-password -a "$USER" -s "$KEY_NAME" -w 2>/dev/null | tr -d '\n')
|
||||||
|
if [ -z "$API_TOKEN" ]; then
|
||||||
|
echo -e "${RED}❌ API token not found in Keychain ($KEY_NAME)${RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== CALL API =====
|
||||||
|
RESPONSE=$(curl -s -H "Authorization: $API_TOKEN" "$URL")
|
||||||
|
|
||||||
|
if [ "$VERBOSE" -eq 1 ]; then
|
||||||
|
echo -e "${YELLOW}🔧 Raw response:${RESET}"
|
||||||
|
echo "$RESPONSE" | jq .
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== PARSE =====
|
||||||
|
SUCCESS=$(echo "$RESPONSE" | jq -r '.success // false')
|
||||||
|
if [ "$SUCCESS" != "true" ]; then
|
||||||
|
echo -e "${RED}❌ API request failed${RESET}"
|
||||||
|
echo "$RESPONSE" | jq .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
LEVEL=$(echo "$RESPONSE" | jq -r '.data.level // "unknown"')
|
||||||
|
|
||||||
|
# Extract TOKENS_LIMIT using jq in one shot
|
||||||
|
read -r T_PERCENT T_RESET T_NUM < <(echo "$RESPONSE" | jq -r '
|
||||||
|
.data.limits[] | select(.type == "TOKENS_LIMIT") |
|
||||||
|
"\(.percentage // 0) \(.nextResetTime // 0) \(.number // 5)"
|
||||||
|
')
|
||||||
|
|
||||||
|
# Extract TIME_LIMIT (web search) in one shot
|
||||||
|
read -r R_USAGE R_CURRENT R_REMAINING R_PERCENT R_RESET < <(echo "$RESPONSE" | jq -r '
|
||||||
|
.data.limits[] | select(.type == "TIME_LIMIT") |
|
||||||
|
"\(.usage // 0) \(.currentValue // 0) \(.remaining // 0) \(.percentage // 0) \(.nextResetTime // 0)"
|
||||||
|
')
|
||||||
|
|
||||||
|
# Convert millisecond timestamps to human readable
|
||||||
|
format_ms() {
|
||||||
|
local ms="$1"
|
||||||
|
if [ -n "$ms" ] && [ "$ms" -gt 0 ] 2>/dev/null; then
|
||||||
|
date -r $((ms / 1000)) "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$ms"
|
||||||
|
else
|
||||||
|
echo "N/A"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
T_RESET_HUMAN=$(format_ms "$T_RESET")
|
||||||
|
R_RESET_HUMAN=$(format_ms "$R_RESET")
|
||||||
|
|
||||||
|
# ===== DISPLAY =====
|
||||||
|
echo -e "${GREEN}✅ GLM Quota ${DIM}(account: ${LEVEL})${RESET}"
|
||||||
|
|
||||||
|
# -- Token Limit (main) --
|
||||||
|
echo -e "\n${CYAN}[Token Limit — ${T_NUM}h rolling]${RESET}"
|
||||||
|
echo -e " Usage: ${YELLOW}${T_PERCENT}%${RESET}"
|
||||||
|
echo " Next Reset: $T_RESET_HUMAN"
|
||||||
|
|
||||||
|
# -- Web Search Limit (secondary) --
|
||||||
|
if [ -n "$R_USAGE" ] && [ "$R_USAGE" != "0" ]; then
|
||||||
|
echo -e "\n${DIM}[Web Search Limit — monthly]${RESET}"
|
||||||
|
echo -e " ${DIM}Used: ${R_CURRENT}/${R_USAGE} (${R_PERCENT}%) | Reset: ${R_RESET_HUMAN}${RESET}"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Function to run SSH command and handle errors
|
||||||
|
run_ssh() {
|
||||||
|
server=$1
|
||||||
|
command=$2
|
||||||
|
echo "===== $server ====="
|
||||||
|
ssh -T $server "$command" &
|
||||||
|
|
||||||
|
# Capture the PID of the last background process
|
||||||
|
pid=$!
|
||||||
|
|
||||||
|
# Wait for the background process to finish
|
||||||
|
wait $pid
|
||||||
|
|
||||||
|
# Check the exit status of the background process
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully upgraded $server."
|
||||||
|
else
|
||||||
|
echo "Failed to upgrade $server. Check the connection and try again."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run SSH commands for each server
|
||||||
|
run_ssh "oc" 'sudo apt-get upgrade -y'
|
||||||
|
run_ssh "tk" 'sudo apt-get upgrade -y'
|
||||||
|
run_ssh "cc" 'sudo apt-get upgrade -y'
|
||||||
|
run_ssh "rpi3" 'sudo apt-get upgrade -y'
|
||||||
|
run_ssh "cse-z" 'sudo apt-get upgrade -y'
|
||||||
|
run_ssh "hp" 'sudo apt-get upgrade -y'
|
||||||
|
run_ssh "od-xu4" 'yay'
|
||||||
|
|
||||||
|
echo "Upgrade process completed for all servers."
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# up-s.sh - Upload file lên server oc
|
||||||
|
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo "Usage: $0 <local-file>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOCAL_FILE=$1
|
||||||
|
REMOTE_HOST="oc"
|
||||||
|
REMOTE_PATH="/app/thuanle.me/s.thuanle.me/data/"
|
||||||
|
|
||||||
|
if [ ! -f "$LOCAL_FILE" ]; then
|
||||||
|
echo "Error: File '$LOCAL_FILE' không tồn tại."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Đang upload $LOCAL_FILE lên $REMOTE_HOST:$REMOTE_PATH ..."
|
||||||
|
scp "$LOCAL_FILE" "${REMOTE_HOST}:${REMOTE_PATH}"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Upload thành công."
|
||||||
|
else
|
||||||
|
echo "Upload thất bại."
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
Reference in New Issue
Block a user