From f1bacee71913046ef5f6e9ca8c823f86167d29ba Mon Sep 17 00:00:00 2001 From: Jerry Chae Date: Thu, 14 May 2026 08:47:58 +0900 Subject: [PATCH] =?UTF-8?q?superfile=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 40 +++++++ install-superfile.ps1 | 248 ++++++++++++++++++++++++++++++++++++++++++ install-superfile.sh | 248 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 536 insertions(+) create mode 100644 install-superfile.ps1 create mode 100755 install-superfile.sh diff --git a/README.md b/README.md index 1329ea0..46fee7b 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ - [tmux](https://github.com/tmux/tmux) - 터미널 멀티플렉서 - [bottom](https://github.com/ClementTsang/bottom) - 호스트 모니터링 +- [superfile](https://github.com/yorukot/superfile) - 터미널 파일 관리자 - [dua](https://github.com/byron/dua-cli) - 디스크 사용량 분석기 - [lazygit](https://github.com/jesseduffield/lazygit) - Git 터미널 UI - [Docker](https://www.docker.com/) - 컨테이너 플랫폼 (Linux 자동 설치, Mac/Windows는 안내) @@ -29,6 +30,12 @@ curl -fsSL "https://gl.isitsoft.kr/publicproj/install-sh/-/raw/main/install-tmux curl -fsSL "https://gl.isitsoft.kr/publicproj/install-sh/-/raw/main/install-bottom.sh?ref_type=heads" | bash ``` +**superfile:** + +```bash +curl -fsSL "https://gl.isitsoft.kr/publicproj/install-sh/-/raw/main/install-superfile.sh?ref_type=heads" | bash +``` + **dua:** ```bash @@ -98,6 +105,12 @@ powershell -ExecutionPolicy Bypass -c "[Net.ServicePointManager]::SecurityProtoc powershell -ExecutionPolicy Bypass -c "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $t=\"$env:TEMP\install-bottom.ps1\"; iwr -useb 'https://gl.isitsoft.kr/publicproj/install-sh/-/raw/main/install-bottom.ps1' -OutFile $t; & $t" ``` +**superfile:** + +```powershell +powershell -ExecutionPolicy Bypass -c "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $t=\"$env:TEMP\install-superfile.ps1\"; iwr -useb 'https://gl.isitsoft.kr/publicproj/install-sh/-/raw/main/install-superfile.ps1' -OutFile $t; & $t" +``` + **dua:** ```powershell @@ -201,6 +214,33 @@ btm | `k` | 선택한 프로세스 종료 | | `/` | 검색 | +### superfile + +터미널 기반 파일 관리자입니다. vim 스타일 키바인딩으로 직관적인 파일 탐색과 관리가 가능합니다. + +- **듀얼 패널** — 좌우 패널로 파일/디렉토리 탐색 +- **vim 스타일 키바인딩** — h/j/k/l 이동, y/d/yank/delete +- **파일 미리보기** — 텍스트, 이미지, 코드 하이라이트 +- **대량 작업** — 선택 후 yank/delete/rename 일괄 처리 +- **플러그인 지원** — 확장 가능한 아키텍처 + +```bash +spf +``` + +| 단축키 | 동작 | +| ----------- | ------------------------ | +| `q` | 종료 | +| `h` `j` `k` `l` | 좌/하/상/우 이동 | +| `Enter` | 디렉토리 진입 / 파일 열기 | +| `y` | Yank (복사) | +| `d` | Delete (삭제) | +| `r` | Rename | +| `/` | 검색 | +| `Tab` | 패널 전환 | + +> **참고**: 실행 명령어는 `spf`입니다 (`superfile`이 아님). + ### dua 디스크 사용량 분석 도구입니다. 병렬 처리로 빠르게 디스크 사용량을 분석합니다. diff --git a/install-superfile.ps1 b/install-superfile.ps1 new file mode 100644 index 0000000..e104383 --- /dev/null +++ b/install-superfile.ps1 @@ -0,0 +1,248 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Installs superfile terminal file manager from GitHub releases. + +.DESCRIPTION + Auto-detects the latest release from + https://github.com/yorukot/superfile/releases, downloads the zip + file matching your Windows architecture, and installs it. + + Supported architectures: + - x64 (AMD64 / Intel 64bit) + - ARM64 (Snapdragon X Elite, Qualcomm ARM, etc.) + +.PARAMETER InstallDir + Directory to install spf.exe. Default: $env:USERPROFILE\.local\bin + +.PARAMETER AddToPath + Whether to permanently add InstallDir to user PATH. Default: $true + +.EXAMPLE + .\install-superfile.ps1 + .\install-superfile.ps1 -InstallDir "C:\tools\bin" + .\install-superfile.ps1 -AddToPath $false + + # One-liner install from remote (works on Windows 10+): + powershell -ExecutionPolicy Bypass -c "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $t=\"$env:TEMP\install-superfile.ps1\"; iwr -useb 'https://gl.isitsoft.kr/publicproj/install-sh/-/raw/main/install-superfile.ps1' -OutFile $t; & $t" + + # With parameters: + powershell -ExecutionPolicy Bypass -c "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $t=\"$env:TEMP\install-superfile.ps1\"; iwr -useb 'https://gl.isitsoft.kr/publicproj/install-sh/-/raw/main/install-superfile.ps1' -OutFile $t; & $t -InstallDir 'C:\tools\bin'" + +.NOTES + If execution policy error occurs: + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +#> + +[CmdletBinding()] +param( + [string] $InstallDir = "$env:USERPROFILE\.local\bin", + [bool] $AddToPath = $true +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# ──────────────────────────────────────────── +# Constants +# ──────────────────────────────────────────── +$REPO = "yorukot/superfile" +$API_URL = "https://api.github.com/repos/$REPO/releases" + +# ──────────────────────────────────────────── +# Utility functions +# ──────────────────────────────────────────── +function Write-Info { param([string]$msg) Write-Host "[INFO] $msg" -ForegroundColor Cyan } +function Write-Warn { param([string]$msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow } +function Write-Success { param([string]$msg) Write-Host "[OK] $msg" -ForegroundColor Green } +function Write-Err { param([string]$msg) Write-Host "[ERROR] $msg" -ForegroundColor Red; exit 1 } + +# ──────────────────────────────────────────── +# Enforce TLS 1.2+ for HTTPS downloads (required on Windows 10 / PowerShell 5.1) +# ──────────────────────────────────────────── +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# ──────────────────────────────────────────── +# Architecture detection +# ──────────────────────────────────────────── +function Get-Platform { + $envArch = $env:PROCESSOR_ARCHITEW6432 + if (-not $envArch) { $envArch = $env:PROCESSOR_ARCHITECTURE } + + switch -Regex ($envArch) { + '^AMD64$' { return "windows-amd64" } + '^ARM64$' { return "windows-arm64" } + '^x86$' { Write-Err "32-bit Windows is not supported by superfile." } + } + + try { + $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture + switch ($arch) { + 'X64' { return "windows-amd64" } + 'Arm64' { return "windows-arm64" } + 'X86' { Write-Err "32-bit Windows is not supported by superfile." } + default { Write-Err "Unsupported architecture: $arch" } + } + } catch { + Write-Err "Failed to detect architecture: $_" + } +} + +# ──────────────────────────────────────────── +# Get latest stable release tag +# ──────────────────────────────────────────── +function Get-LatestTag { + Write-Info "Fetching latest release from GitHub API..." + + try { + $releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing ` + -Headers @{ 'User-Agent' = 'install-superfile-ps1' } + } catch { + Write-Err "Failed to call GitHub API: $_" + } + + $latest = $releases | Where-Object { $_.prerelease -eq $false } | Select-Object -First 1 + + if (-not $latest) { + Write-Err "Could not find latest release." + } + + return $latest.tag_name +} + +# ──────────────────────────────────────────── +# Build download URL +# ──────────────────────────────────────────── +function Get-DownloadUrl { + param([string]$Tag, [string]$Platform) + # Asset naming: superfile-windows-v1.5.0-amd64.zip + $version = $Tag -replace '^v', '' + $filename = "superfile-${Platform}-${Tag}.zip" + return "https://github.com/$REPO/releases/download/$Tag/$filename" +} + +# ──────────────────────────────────────────── +# Download, extract & install +# ──────────────────────────────────────────── +function Install-Superfile { + param([string]$Url) + + $tmpDir = Join-Path $env:TEMP "superfile_install_$(Get-Random)" + New-Item -ItemType Directory -Path $tmpDir | Out-Null + + try { + $zipPath = Join-Path $tmpDir "superfile.zip" + + Write-Info "Downloading: $Url" + try { + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing + $ProgressPreference = 'Continue' + } catch { + Write-Err "Download failed: $_" + } + + Write-Info "Extracting..." + Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force + + $spfExe = Get-ChildItem -Path $tmpDir -Recurse -Filter "spf.exe" | + Select-Object -First 1 + if (-not $spfExe) { + Write-Err "Cannot find 'spf.exe' in the archive." + } + + if (-not (Test-Path $InstallDir)) { + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + } + + $destPath = Join-Path $InstallDir "spf.exe" + + # Check if spf.exe is currently running (locked) + if (Test-Path $destPath) { + try { + $testStream = [System.IO.File]::Open($destPath, 'Open', 'ReadWrite', 'Delete') + $testStream.Close() + $testStream.Dispose() + } catch { + Write-Err "spf.exe is currently running. Please close superfile and run this script again." + } + } + + Write-Info "Installing: $destPath" + Copy-Item -Path $spfExe.FullName -Destination $destPath -Force + + Write-Success "Installation complete!" + + Write-Info "Version check:" + try { + & $destPath --version + } catch { + Write-Warn "Cannot run version check (spf may need a new terminal session to start)." + } + + } finally { + if (Test-Path $tmpDir) { + Remove-Item -Path $tmpDir -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + +# ──────────────────────────────────────────── +# Add to PATH (user environment variable) +# ──────────────────────────────────────────── +function Add-ToUserPath { + param([string]$Dir) + + $currentPath = [Environment]::GetEnvironmentVariable('PATH', 'User') + if (-not $currentPath) { $currentPath = '' } + $dirs = @($currentPath -split ';' | Where-Object { $_ -ne '' }) + + if ($dirs -contains $Dir) { + Write-Info "'$Dir' is already in your PATH." + return + } + + $newPath = ($dirs + $Dir) -join ';' + [Environment]::SetEnvironmentVariable('PATH', $newPath, 'User') + + $env:PATH = "$env:PATH;$Dir" + + Write-Success "'$Dir' has been permanently added to user PATH." + Write-Warn "Open a new terminal for PATH changes to take effect." +} + +# ──────────────────────────────────────────── +# Main +# ──────────────────────────────────────────── +function Main { + Write-Info "===== superfile install script (Windows PowerShell) =====" + + $platform = Get-Platform + Write-Info "Detected platform: $platform" + + $tag = Get-LatestTag + Write-Info "Latest release: $tag" + + $url = Get-DownloadUrl -Tag $tag -Platform $platform + Install-Superfile -Url $url + + if ($AddToPath) { + Add-ToUserPath -Dir $InstallDir + } else { + Write-Warn "Skipping automatic PATH registration." + Write-Warn "Add to PATH manually: $InstallDir" + } + + Write-Success "All tasks completed." + Write-Info "" + Write-Info "IMPORTANT: Close this terminal and open a new one to use superfile (spf)." + Write-Info "PATH changes require a new terminal session to take effect." + Write-Info "" + Write-Info "Getting started:" + Write-Info " spf - Start superfile file manager" + Write-Info " spf --help - Show help" + Write-Info "" + Write-Info "Note: The command name is 'spf', not 'superfile'." +} + +Main \ No newline at end of file diff --git a/install-superfile.sh b/install-superfile.sh new file mode 100755 index 0000000..d010918 --- /dev/null +++ b/install-superfile.sh @@ -0,0 +1,248 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2086 + +# Downloads and installs the latest superfile from +# https://github.com/yorukot/superfile/releases +# +# Supported platforms: +# macOS : Apple Silicon (arm64), Intel (x86_64) — via Homebrew or direct download +# Linux : x86_64 (amd64), aarch64/arm64 — via direct download +# Windows : Not supported — use install-superfile.ps1 (PowerShell) + +set -euo pipefail + +# ──────────────────────────────────────────── +# Constants +# ──────────────────────────────────────────── +REPO="yorukot/superfile" +API_URL="https://api.github.com/repos/${REPO}/releases" +INSTALL_DIR="${HOME}/.local/bin" + +# ──────────────────────────────────────────── +# Utility functions (all stderr output) +# ──────────────────────────────────────────── +info() { echo "[INFO] $*" >&2; } +warn() { echo "[WARN] $*" >&2; } +error() { echo "[ERROR] $*" >&2; exit 1; } +success() { echo "[OK] $*" >&2; } + +# ──────────────────────────────────────────── +# Ensure Homebrew is available on macOS +# ──────────────────────────────────────────── +ensure_brew() { + if command -v brew &>/dev/null; then + return 0 + fi + info "Homebrew is not installed. Installing Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + if [ -d /opt/homebrew/bin ]; then + export PATH="/opt/homebrew/bin:$PATH" + fi + if [ -d /usr/local/bin ]; then + export PATH="/usr/local/bin:$PATH" + fi + command -v brew &>/dev/null || error "Homebrew installation failed. Install it manually: https://brew.sh/" + success "Homebrew installed" +} + +# ──────────────────────────────────────────── +# Check required commands +# ──────────────────────────────────────────── +for cmd in curl uname; do + command -v "$cmd" &>/dev/null || error "'$cmd' command is required." +done + +# Fallback: use python3 if jq is not available +if command -v jq &>/dev/null; then + _json_latest_tag() { + jq -r '[.[] | select(.prerelease==false)] | .[0].tag_name' + } +else + command -v python3 &>/dev/null || error "'jq' or 'python3' is required." + _json_latest_tag() { + python3 -c " +import sys, json +data = json.load(sys.stdin) +for r in data: + if not r.get('prerelease'): + print(r['tag_name']) + break +" + } +fi + +# ──────────────────────────────────────────── +# OS / Architecture detection +# ──────────────────────────────────────────── +OS="$(uname -s)" +ARCH="$(uname -m)" + +case "$OS" in +Darwin | Linux) ;; +*) + error "Unsupported OS: $OS. This script only supports macOS and Linux." + ;; +esac + +# Returns: ":" +detect_platform() { + case "$OS" in + Darwin) + case "$ARCH" in + arm64) echo "darwin-arm64:tar.gz" ;; + x86_64) echo "darwin-amd64:tar.gz" ;; + *) error "Unsupported macOS architecture: $ARCH" ;; + esac + ;; + Linux) + case "$ARCH" in + x86_64|amd64) echo "linux-amd64:tar.gz" ;; + aarch64|arm64) echo "linux-arm64:tar.gz" ;; + *) error "Unsupported Linux architecture: $ARCH" ;; + esac + ;; +esac +} + +# ──────────────────────────────────────────── +# Get latest stable release tag +# ──────────────────────────────────────────── +get_latest_tag() { + info "Fetching latest release from GitHub API..." + + local releases + releases="$(curl -fsSL "${API_URL}")" || error "Failed to call GitHub API." + + local tag + tag="$(echo "$releases" | _json_latest_tag)" + + [[ -z "$tag" || "$tag" == "null" ]] && error "Could not find latest release tag." + echo "$tag" +} + +# ──────────────────────────────────────────── +# Build download URL +# ──────────────────────────────────────────── +# Asset naming: superfile-{os}-{tag_version}-{arch}.tar.gz +build_download_url() { + local tag="$1" + local platform="$2" # e.g. darwin-arm64 + local ext="$3" # tar.gz | zip + local version="${tag#v}" + + local filename="superfile-${platform}-${tag}.${ext}" + echo "https://github.com/${REPO}/releases/download/${tag}/${filename}" +} + +# ──────────────────────────────────────────── +# Download, extract & install +# ──────────────────────────────────────────── +download_and_install() { + local url="$1" + local ext="$2" + + local tmpdir + tmpdir="$(mktemp -d)" + trap '[[ -d "${tmpdir:-}" ]] && rm -rf "$tmpdir"' EXIT + + local filename + filename="${url##*/}" + + info "Downloading: $url" + curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url" \ + || error "Download failed: $url" + + info "Extracting..." + case "$ext" in + tar.gz) + command -v tar &>/dev/null || error "'tar' command is required." + tar -xzf "${tmpdir}/${filename}" -C "$tmpdir" + ;; + zip) + if command -v unzip &>/dev/null; then + unzip -q "${tmpdir}/${filename}" -d "$tmpdir" + elif command -v tar &>/dev/null; then + tar -xf "${tmpdir}/${filename}" -C "$tmpdir" + else + error "'unzip' or 'tar' is required." + fi + ;; + *) + error "Unknown archive format: $ext" + ;; + esac + + # Binary name + local bin_name="spf" + [[ "$OS" == "MINGW"* || "$OS" == "MSYS"* || "$OS" == "CYGWIN"* ]] && bin_name="spf.exe" + + local bin_src + bin_src="$(find "$tmpdir" -maxdepth 3 -type f -name "$bin_name" | head -1)" + [[ -z "$bin_src" ]] && error "Cannot find '$bin_name' in the archive." + + # Create install directory + mkdir -p "$INSTALL_DIR" + + local install_path="${INSTALL_DIR}/${bin_name}" + info "Installing: ${install_path}" + cp "$bin_src" "$install_path" + chmod +x "$install_path" + + success "Installation complete!" + info "Version check:" + "$install_path" --version || true + + # PATH instructions + if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then + warn "'${INSTALL_DIR}' is not in your PATH." + warn "Add this line to your ~/.bashrc or ~/.zshrc:" + warn " export PATH=\"\$HOME/.local/bin:\$PATH\"" + fi +} + +# ──────────────────────────────────────────── +# Main +# ──────────────────────────────────────────── +main() { + local platform_info platform ext tag url + + platform_info="$(detect_platform)" + platform="${platform_info%%:*}" + ext="${platform_info##*:}" + + info "Detected platform: $platform (format: $ext)" + + # macOS: try Homebrew first + if [[ "$OS" == "Darwin" ]]; then + if command -v spf &>/dev/null; then + local current_ver + current_ver="$(spf --version 2>/dev/null || echo 'unknown')" + info "superfile is already installed ($current_ver). Skipping installation." + else + ensure_brew + brew install superfile + success "superfile installed via Homebrew" + fi + else + if command -v spf &>/dev/null; then + local current_ver + current_ver="$(spf --version 2>/dev/null || echo 'unknown')" + info "superfile is already installed ($current_ver). Skipping installation." + else + tag="$(get_latest_tag)" + info "Latest release: $tag" + + url="$(build_download_url "$tag" "$platform" "$ext")" + download_and_install "$url" "$ext" + fi + fi + + # Verify spf is available + if command -v spf &>/dev/null; then + success "superfile is available: $(command -v spf)" + else + warn "superfile was not found in PATH after installation. You may need to open a new terminal." + fi +} + +main "$@" \ No newline at end of file