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 "========================================="