From e94f44dfaead04efd24c0ccffa4d31d7c54b6c01 Mon Sep 17 00:00:00 2001 From: phuonghx Date: Sun, 14 Jun 2026 23:47:53 +0700 Subject: [PATCH] fix(install): detect Scripts/bin dir and offer to add it to PATH The "'aim' is not recognized" / "command not found" error right after install is almost always pip placing the console script in a folder that is not on PATH. Both one-line installers now locate that folder via sysconfig, and when running interactively offer to append it to the user PATH (also updating the current session so 'aim' works right away). Non-interactive runs print manual instructions instead of changing PATH. Document the fix plus a no-PATH `python -m aim.aim_cli` workaround in both README.md and README-VI.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- README-VI.md | 49 ++++++++++++++++++++++ README.md | 37 +++++++++++++++++ install.ps1 | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++- install.sh | 102 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 298 insertions(+), 4 deletions(-) diff --git a/README-VI.md b/README-VI.md index 978737e..fe2f850 100644 --- a/README-VI.md +++ b/README-VI.md @@ -47,6 +47,55 @@ Sau khi chạy xong, bộ cài sẽ tự động tạo: - `aim.bat`: Cho phép bạn gõ trực tiếp `aim ` trong CMD hoặc PowerShell. - `aim.sh`: Cho phép bạn gõ `aim` trên môi trường Unix/Linux/macOS. +### Cài một dòng (one-line install) + +**Windows (PowerShell):** +```powershell +iwr -useb https://raw.githubusercontent.com/phuonghx/aim-cli/main/install.ps1 | iex +``` + +**macOS / Linux:** +```bash +curl -fsSL https://raw.githubusercontent.com/phuonghx/aim-cli/main/install.sh | bash +``` + +Sau khi cài, chạy `aim init` trong thư mục dự án để bắt đầu. + +### Khắc phục lỗi `'aim' is not recognized` / `command not found` + +Lỗi này gần như luôn do pip cài file thực thi `aim` vào thư mục **Scripts** +(Windows) hoặc **bin** (macOS/Linux) chưa nằm trong `PATH`. Ngay khi cài, pip +thường in cảnh báo: `WARNING: The script aim is installed in '...' which is not on PATH`. + +Bộ cài một dòng nay đã **tự phát hiện** và **hỏi bạn có muốn thêm vào PATH không** +(gõ `Y` để đồng ý), sau đó nhắc bạn mở terminal mới. Nếu vẫn lỗi, sửa thủ công: + +**Windows (PowerShell)** — tìm thư mục Scripts rồi thêm vào PATH của user: +```powershell +# Xem thư mục chứa console scripts (kiểm tra cả 2 dòng xem dòng nào có aim.exe): +python -c "import sysconfig; print(sysconfig.get_paths('nt_user')['scripts']); print(sysconfig.get_paths('nt')['scripts'])" + +# Thêm đúng thư mục (thay đường dẫn) vào PATH user, rồi mở lại terminal: +$d = "C:\Users\\AppData\Roaming\Python\Python3XX\Scripts" +[Environment]::SetEnvironmentVariable('Path', [Environment]::GetEnvironmentVariable('Path','User').TrimEnd(';') + ';' + $d, 'User') +``` +> Lưu ý: câu lỗi `'aim' is not recognized as an internal or external command` là +> của **cmd.exe**. Nếu bạn cài bằng PowerShell thì cũng nên chạy `aim` trong PowerShell. + +**macOS / Linux** — thêm thư mục bin vào file khởi động của shell: +```bash +# Xem thư mục chứa console scripts (thường là ~/.local/bin): +python3 -c "import sysconfig; print(sysconfig.get_paths('posix_user')['scripts'])" + +echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.zshrc # hoặc ~/.bashrc +source ~/.zshrc +``` + +**Cách chạy tạm không cần sửa PATH (mọi hệ điều hành):** +```bash +python -m aim.aim_cli init +``` + --- ## 🔄 Đồng Bộ Hóa Hướng Dẫn (Sync) diff --git a/README.md b/README.md index d57f695..4738c2a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,43 @@ aim ingest --dry-run # preview aim ingest && aim sync # consolidate, then re-emit everywhere ``` +### Troubleshooting: `aim` is not recognized / command not found + +This almost always means pip installed the `aim` executable into a **Scripts** +folder (Windows) or **bin** folder (macOS/Linux) that is not on your `PATH`. +During install pip even prints a hint such as +`WARNING: The script aim is installed in '...' which is not on PATH`. + +The one-line installers now detect this and offer to add the folder to your +`PATH` for you (answer `Y` at the prompt), then ask you to open a new terminal. +If you still hit the error, fix it manually: + +**Windows (PowerShell)** — find the folder and add it to your user PATH: +```powershell +# Show where the console scripts live (check both lines for aim.exe): +python -c "import sysconfig; print(sysconfig.get_paths('nt_user')['scripts']); print(sysconfig.get_paths('nt')['scripts'])" + +# Add the right folder (replace the path) to your user PATH, then reopen the terminal: +$d = "C:\Users\\AppData\Roaming\Python\Python3XX\Scripts" +[Environment]::SetEnvironmentVariable('Path', [Environment]::GetEnvironmentVariable('Path','User').TrimEnd(';') + ';' + $d, 'User') +``` +> Tip: the error text `'aim' is not recognized as an internal or external command` +> is from **cmd.exe**. If you installed in PowerShell, run `aim` in PowerShell too. + +**macOS / Linux** — add the bin folder to your shell startup file: +```bash +# Show where the console scripts live (usually ~/.local/bin): +python3 -c "import sysconfig; print(sysconfig.get_paths('posix_user')['scripts'])" + +echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.zshrc # or ~/.bashrc +source ~/.zshrc +``` + +**No-PATH workaround (any OS)** — run AIM as a module without changing PATH: +```bash +python -m aim.aim_cli init +``` + ### From a repository checkout (development) Run via the included wrappers without installing: - `aim.bat` (Windows) / `aim.sh` (Unix-like shells) at the repo root. diff --git a/install.ps1 b/install.ps1 index 91e1538..929fd18 100644 --- a/install.ps1 +++ b/install.ps1 @@ -45,6 +45,116 @@ Write-Host "" Write-Host "=========================================" -ForegroundColor Green Write-Host " [+] AIM installed successfully! " -ForegroundColor Green Write-Host "=========================================" -ForegroundColor Green -Write-Host "You can now run 'aim' command directly from your terminal." -Write-Host "Run 'aim init' in your project directory to get started." + +# 4. Make sure the 'aim' command is reachable (fix the classic +# "'aim' is not recognized..." problem) by locating the Scripts folder +# where pip placed aim.exe and optionally adding it to the user PATH. + +function Get-AimScriptDir { + param([string]$PythonCmd) + + # If 'aim' already resolves, use its real folder. + $cmd = Get-Command aim -ErrorAction SilentlyContinue + if ($cmd -and $cmd.Source) { return (Split-Path -Parent $cmd.Source) } + + # Ask Python where console scripts are installed (user scheme first, then global). + $pyCode = @' +import sysconfig +dirs = [] +for scheme in ("nt_user", "nt"): + try: + d = sysconfig.get_paths(scheme).get("scripts") + if d: + dirs.append(d) + except Exception: + pass +for d in dirs: + print(d) +'@ + $candidates = & $PythonCmd -c $pyCode + + # Prefer a folder that actually contains aim.exe. + foreach ($d in $candidates) { + if ($d -and (Test-Path (Join-Path $d 'aim.exe'))) { return $d } + } + if ($candidates) { return ($candidates | Select-Object -First 1) } + return $null +} + +function Test-OnPath { + param([string]$Dir) + if (-not $Dir) { return $false } + $norm = $Dir.TrimEnd('\').ToLowerInvariant() + foreach ($p in ($env:Path -split ';')) { + if ($p -and ($p.TrimEnd('\').ToLowerInvariant() -eq $norm)) { return $true } + } + return $false +} + +$scriptDir = Get-AimScriptDir -PythonCmd $pythonCmd + +if (Test-OnPath $scriptDir) { + Write-Host "[+] 'aim' is on your PATH - you can run it directly." -ForegroundColor Green +} +elseif ($scriptDir) { + Write-Host "[!] The 'aim' command was installed to:" -ForegroundColor Yellow + Write-Host " $scriptDir" -ForegroundColor Yellow + Write-Host " but this folder is NOT on your PATH, so typing 'aim' won't work yet." -ForegroundColor Yellow + Write-Host "" + + $doAdd = $false + $canPrompt = -not [Console]::IsInputRedirected + if ($canPrompt) { + try { + $answer = Read-Host "Add this folder to your user PATH now? [Y/n]" + $answer = "$answer".Trim() + if ($answer -eq '' -or $answer -match '^(y|yes)$') { $doAdd = $true } + } catch { + $canPrompt = $false + } + } + if (-not $canPrompt) { + Write-Host "[*] Non-interactive session detected; not changing PATH automatically." -ForegroundColor DarkGray + } + + if ($doAdd) { + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + $already = $false + if ($userPath) { + foreach ($p in ($userPath -split ';')) { + if ($p -and ($p.TrimEnd('\').ToLowerInvariant() -eq $scriptDir.TrimEnd('\').ToLowerInvariant())) { + $already = $true; break + } + } + } + if (-not $already) { + $newPath = if ([string]::IsNullOrEmpty($userPath)) { $scriptDir } else { ($userPath.TrimEnd(';') + ';' + $scriptDir) } + [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') + Write-Host "[+] Added to your user PATH." -ForegroundColor Green + } else { + Write-Host "[+] Already present in your user PATH." -ForegroundColor Green + } + # Update the current window too, so 'aim' works right away here. + $env:Path = "$env:Path;$scriptDir" + Write-Host "[+] Updated PATH for THIS window. Open a NEW terminal for it to apply everywhere." -ForegroundColor Green + } else { + Write-Host "" + Write-Host "To add it later, paste this into PowerShell:" -ForegroundColor Cyan + Write-Host " `$d = '$scriptDir'" -ForegroundColor White + Write-Host " [Environment]::SetEnvironmentVariable('Path', [Environment]::GetEnvironmentVariable('Path','User').TrimEnd(';') + ';' + `$d, 'User')" -ForegroundColor White + Write-Host " # then close and reopen your terminal" -ForegroundColor DarkGray + Write-Host "" + Write-Host "Or run AIM without touching PATH:" -ForegroundColor Cyan + Write-Host " $pythonCmd -m aim.aim_cli init" -ForegroundColor White + } +} +else { + Write-Host "[!] Could not locate the 'aim' executable automatically." -ForegroundColor Yellow + Write-Host " You can still run it with: $pythonCmd -m aim.aim_cli init" -ForegroundColor White +} + +Write-Host "" +Write-Host "Get started:" -ForegroundColor Cyan +Write-Host " aim init # run inside your project directory" -ForegroundColor White +Write-Host " aim --help" -ForegroundColor White Write-Host "=========================================" diff --git a/install.sh b/install.sh index 63c6250..77e8b7f 100644 --- a/install.sh +++ b/install.sh @@ -40,6 +40,104 @@ echo "" echo "=========================================" echo " [+] AIM installed successfully! " echo "=========================================" -echo "You can now run 'aim' command directly from your terminal." -echo "Run 'aim init' in your project directory to get started." + +# 4. Make sure the 'aim' command is reachable. pip often installs console +# scripts into a folder (e.g. ~/.local/bin) that is not on PATH, which is +# why 'aim' can be "command not found" right after install. Locate it and +# optionally add it to PATH. From here on, don't abort on minor errors - +# the install itself already succeeded. +set +e + +# Find the folder where the 'aim' executable landed. +AIM_DIR="" +if command -v aim &>/dev/null; then + AIM_DIR="$(dirname "$(command -v aim)")" +else + CANDIDATES="$($PYTHON_CMD -c 'import sysconfig +seen = [] +try: + seen.append(sysconfig.get_paths("posix_user")["scripts"]) +except Exception: + pass +try: + seen.append(sysconfig.get_path("scripts")) +except Exception: + pass +print("\n".join([p for p in seen if p]))')" + while IFS= read -r d; do + [ -z "$d" ] && continue + if [ -x "$d/aim" ]; then AIM_DIR="$d"; break; fi + [ -z "$AIM_DIR" ] && AIM_DIR="$d" + done <<< "$CANDIDATES" +fi + +# Is AIM_DIR already on PATH? +on_path() { + case ":$PATH:" in + *":$1:"*) return 0 ;; + *) return 1 ;; + esac +} + +if [ -z "$AIM_DIR" ]; then + echo "[!] Could not locate the 'aim' executable automatically." + echo " You can still run it with: $PYTHON_CMD -m aim.aim_cli init" +elif on_path "$AIM_DIR"; then + echo "[+] 'aim' is on your PATH - you can run it directly." +else + echo "[!] The 'aim' command was installed to:" + echo " $AIM_DIR" + echo " but this folder is NOT on your PATH, so typing 'aim' won't work yet." + echo "" + + # Pick the right shell startup file. + SHELL_NAME="$(basename "${SHELL:-bash}")" + case "$SHELL_NAME" in + zsh) RC_FILE="$HOME/.zshrc" ;; + bash) + if [ "$(uname)" = "Darwin" ]; then RC_FILE="$HOME/.bash_profile"; else RC_FILE="$HOME/.bashrc"; fi + ;; + *) RC_FILE="$HOME/.profile" ;; + esac + + DO_ADD="n" + # Open the real terminal on fd 3. This both tests interactivity and lets us + # read the answer even when the script itself arrived via `curl | bash` + # (where stdin is the pipe, not the keyboard). If /dev/tty can't be opened + # we are non-interactive: do NOT touch PATH, just print instructions. + if { exec 3/dev/null; then + printf "Add this folder to your PATH in %s now? [Y/n] " "$RC_FILE" + read -r ANSWER <&3 || ANSWER="" + exec 3<&- + case "$ANSWER" in + ""|[Yy]|[Yy][Ee][Ss]) DO_ADD="y" ;; + esac + else + echo "[*] Non-interactive session detected; not changing PATH automatically." + fi + + if [ "$DO_ADD" = "y" ]; then + if [ -f "$RC_FILE" ] && grep -Fq "$AIM_DIR" "$RC_FILE"; then + echo "[+] $RC_FILE already references this folder." + else + printf '\n# Added by AIM installer\nexport PATH="$PATH:%s"\n' "$AIM_DIR" >> "$RC_FILE" + echo "[+] Added to $RC_FILE" + fi + export PATH="$PATH:$AIM_DIR" + echo "[+] Updated PATH for this session." + echo " Open a new terminal, or run: source $RC_FILE" + else + echo "" + echo "To add it later, run:" + echo " echo 'export PATH=\"\$PATH:$AIM_DIR\"' >> $RC_FILE && source $RC_FILE" + echo "" + echo "Or run AIM without changing PATH:" + echo " $PYTHON_CMD -m aim.aim_cli init" + fi +fi + +echo "" +echo "Get started:" +echo " aim init # run inside your project directory" +echo " aim --help" echo "========================================="