commit b66b6589c876d3d2ae69d6c8962bbb22b7f8cf90 Author: thuanle Date: Sun Apr 19 21:43:13 2026 +0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..118978d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.config.ini +/*.jar diff --git a/ask b/ask new file mode 100755 index 0000000..9e21923 --- /dev/null +++ b/ask @@ -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] " + exit 1 +fi + +call_api "$*" diff --git a/cleanup.sh b/cleanup.sh new file mode 100755 index 0000000..6edb082 --- /dev/null +++ b/cleanup.sh @@ -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_iddirname" 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 - + 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 =====" \ No newline at end of file diff --git a/glm-quota b/glm-quota new file mode 100755 index 0000000..84785a3 --- /dev/null +++ b/glm-quota @@ -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 diff --git a/supgrade b/supgrade new file mode 100755 index 0000000..5b88505 --- /dev/null +++ b/supgrade @@ -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." diff --git a/up-s.sh b/up-s.sh new file mode 100755 index 0000000..46d6744 --- /dev/null +++ b/up-s.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# up-s.sh - Upload file lên server oc + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + 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 +