Getting a fresh machine set up for AI coding, Claude Code, OpenAI Codex, ChatGPT in the terminal, GitHub Copilot CLI, plus Netlify CLI for deploys, is usually a scavenger hunt across five docs pages, three package managers, and one inevitable PATH issue. This is the condensed walk-through. Every command is copy-pasteable, paired for macOS / Linux (Bash) and Windows (PowerShell) so whichever box you're on, you're one paste away from a working environment. There are now three one-shot scripts — a dedicated macOS edition (Homebrew, zsh, Apple Silicon + Intel), the Linux edition (apt/dnf), and the Windows edition (PowerShell).
The order matters: runtimes first, then CLIs that depend on them, then auth. If you skip a step you'll hit it later.
🧭 Already got the CLI working? The follow-on picks up where this post leaves off: You've Got The Claude Code CLI Installed — Now What? covers plan mode, CLAUDE.md as a living contract, the Q1 2026 slash commands (
/btw/fork/rewind/compact/loop/schedule/simplify/batch/powerup/insights/debug/claude-api), Skills vs Rules vs Memory, Auto Mode boundaries, and a Day 0 → Day 7 ramp plan. Bookmark it for when you're through this install.
📚 The full workflow series after this install primer (this post is #1):
Claude Code track:
- This post — install the CLIs.
- CLI installed — now what? — plan mode, CLAUDE.md, the slash commands that matter, Day 0 → Day 7 ramp.
- Skills, Rules, Memory deep-dive — the four-layer hierarchy that keeps CLAUDE.md from bloating.
- Adversarial subagents + worktrees — parallel Claudes with deliberately different review roles.
- Hooks implementation guide — stop approving the same prompt 40 times a day.
- /loop vs /schedule — in-session polling vs cron-style automation.
- CLAUDE.md anti-patterns — the 8 patterns that rot the file over time.
- Auto Mode case studies — four real classifier calls, wins and losses.
- /insights-driven iteration — the 30-minute Monday ritual.
- Remote Control + Channels — running your session from iMessage, Telegram, Discord.
- Multi-model routing — when to break out of Claude-only.
- Claude Fable 5 in Claude Code — switching models by ID, and why the new top tier was pulled by a US export-control order days after launch.
- Agent skill marketplace — Skills 2.0 ecosystem, evaluating before installing, publishing your own.
Codex track (for readers using OpenAI's CLI or running both):
- Codex CLI after install — OpenAI-specific first-week habits, model selection, cost mechanics.
- Codex vs Claude Code — task-level comparison — where each one actually wins.
- Prompt style — GPT vs Claude — the four stylistic differences that matter in practice.
- ChatGPT CLI / Codex CLI / GitHub Copilot CLI — positioning — three products that sound alike and do different jobs.
- Two-CLI workflow — split-screen routine for running Codex + Claude Code in parallel.
Open-weights + Google-stack track:
- Qwen self-hosted at home — when open-weights beats proprietary, hardware tiers, Ollama setup, quantization trade-offs.
- Gemini CLI — when to use — long context, multimodal, Google-ecosystem fit, and where Gemini falls short against Claude Code.
- Gemma — where it fits next to Qwen — Google's open-weights counterpart; when to pick it over Qwen and vice versa.
What you're installing
| Layer | Tools |
|---|---|
| Runtimes | Node.js LTS, Python 3.12, Git |
| Package managers | Homebrew (macOS), Chocolatey / winget (Windows), apt / dnf (Linux), npm, pip / pipx |
| AI terminals | Claude Code (Anthropic), OpenAI Codex CLI, ChatGPT CLI (OpenAI), GitHub Copilot CLI |
| AI frameworks | aider (pair programmer), llm (pluggable LLM CLI), repomix (repo → one prompt), Gemini CLI, aichat, files-to-prompt |
| Conversions | pandoc (docs), poppler / pdftotext, tesseract (OCR), markitdown (Office/PDF → Markdown for LLMs), ffmpeg, ImageMagick, yt-dlp, jc |
| Deploy | Netlify CLI, Cloudflare Wrangler (Pages + Workers), optional Vercel CLI |
| Utilities | ripgrep, jq, fd, bat, fzf, yq, tree, git-delta, shellcheck, tmux, sqlite3, httpie, ast-grep, gron, tldr, just |
| Optional model (opt-in) | Kimi Code (Moonshot AI) — add with --with-kimi / -WithKimi; leave it off and the stack stays US-only |
The AI terminals differ in what they're good at. Claude Code edits files and runs commands end-to-end inside a repo, best for real work. Codex / ChatGPT CLI is better for quick questions and one-shot generations. Copilot CLI suggests shell commands from natural language. The AI frameworks layer adds agentic helpers (aider, llm, aichat) and repo/document packers (repomix, files-to-prompt, markitdown), while the conversions layer turns PDFs, scans, Office files, audio and video into text an AI model can actually read.
One-shot install: full scripts you can save and run
If you'd rather run an automated installer than paste each of the Step 1-7 blocks individually, the three scripts below do every install + auth wiring in one go. Save the matching file for your OS, run it once, and you end up with Claude Code, OpenAI Codex, ChatGPT CLI, GitHub Copilot CLI, Netlify CLI, the AI framework + conversion bundle (aider, llm, repomix, pandoc, tesseract, markitdown …), and every dependency on your PATH.
All three are downloadable:
- Bash — macOS (Homebrew, Apple Silicon + Intel):
/downloads/start-ai-terminal-kickstart-macos.sh - Bash — Linux / WSL (Ubuntu, Mint, Fedora, RHEL):
/downloads/start-ai-terminal-kickstart.sh - PowerShell — Windows 10/11 / Server 2022:
/downloads/Start-AITerminalKickstart.ps1
Why a separate macOS script? The Linux edition installs through
apt/dnfand runs withsudo. macOS has neither — it uses Homebrew, which refuses to run as root, defaults to zsh (not bash), and ships an ancient bash 3.2. The macOS edition is written for those realities: nosudo, Homebrew-native, zsh-profile aware, and Apple-Silicon/Intel aware.
Save and run — macOS (Bash + Homebrew)
-
Copy the macOS script (expand the "Full macOS script" block below, click inside the code block, then
Ctrl+A→Cmd+C), or download it from the link above. -
Save it as
start-ai-terminal-kickstart-macos.shin your home folder:nano ~/start-ai-terminal-kickstart-macos.sh # paste, then Ctrl+O Enter Ctrl+X -
Make it executable and run it — without
sudo(Homebrew will abort if you use root):chmod +x ~/start-ai-terminal-kickstart-macos.sh ~/start-ai-terminal-kickstart-macos.sh # unattended (yes to everything): ~/start-ai-terminal-kickstart-macos.sh --auto # also install a local model runner / LibreOffice: ~/start-ai-terminal-kickstart-macos.sh --with-ollama --with-office # opt in to the non-US Kimi model (off by default; the stack is US-only otherwise): ~/start-ai-terminal-kickstart-macos.sh --with-kimiFirst run will install Apple's Command Line Tools and Homebrew if they're missing (Homebrew may prompt once for your password — that's expected).
-
Open a fresh terminal so the zsh profile (
~/.zprofile) reloads. Thenclaude,codex,chatgpt,gh copilot,aider,llm, andnetlifyare all on your PATH.
Save and run — Linux / WSL (Bash)
-
Copy the Bash script (expand the "Full Linux script" block below and click inside the code block, then
Ctrl+A→Ctrl+C). -
Save it as
start-ai-terminal-kickstart.shin your home directory:nano ~/start-ai-terminal-kickstart.sh # paste, then Ctrl+O Enter Ctrl+XOr, if you downloaded the file from the link above, move it to
~/and skip to step 3. -
Make it executable and run it:
chmod +x ~/start-ai-terminal-kickstart.sh sudo ~/start-ai-terminal-kickstart.sh # or, unattended (install everything, no prompts): sudo ~/start-ai-terminal-kickstart.sh --auto # opt in to the non-US Kimi model (off by default; US-only otherwise): sudo ~/start-ai-terminal-kickstart.sh --with-kimi -
Close the terminal and open a fresh one so PATH updates take effect. Then
claude,codex,chatgpt,gh copilot, andnetlifyare all on your PATH.
Save and run — Windows (PowerShell)
-
Copy the PowerShell script (expand the "Full PowerShell script" block below, click inside the code block,
Ctrl+A→Ctrl+C). -
Save as
Start-AITerminalKickstart.ps1in your Downloads folder (or wherever you prefer).Open Notepad, paste,
File → Save As…, set "Save as type" to All Files, name the fileStart-AITerminalKickstart.ps1, and save with encoding UTF-8. Or if you downloaded the file from the link above, skip to step 3. -
Open PowerShell as Administrator (Start menu → type
PowerShell→ right-click → Run as administrator), then:# Allow the script to run for this session only Set-ExecutionPolicy Bypass -Scope Process -Force # cd to where you saved it cd $env:USERPROFILE\Downloads # Run it (interactive menu) .\Start-AITerminalKickstart.ps1 # Or unattended — install everything, no prompts: .\Start-AITerminalKickstart.ps1 -AutoMode # Opt in to the non-US Kimi model (off by default; US-only otherwise): .\Start-AITerminalKickstart.ps1 -WithKimi -
Close the PowerShell window and open a fresh one.
claude,codex,chatgpt,gh copilot, andnetlifyshould all resolve.
If PowerShell refuses the script with "cannot be loaded because running scripts is disabled", you skipped step 3's
Set-ExecutionPolicyline. The-Scope Processflag is the safe version, it only lifts the restriction for the current terminal, not system-wide.
Two known install errors on Server 2022 / Win 11 (and the one-liner fixes)
The script auto-detects and works around both of these — they only matter if you're running the install commands by hand outside the script. They are also documented in the script's .NOTES header for offline reference.
1. Chocolatey: "Cannot overwrite variable result because the variable has been optimized."
Chocolatey's install.ps1 was authored for Windows PowerShell 5.1 and trips a known optimization bug in PowerShell 7+. Manual workaround — run from an elevated Windows PowerShell 5.1 (powershell.exe), not pwsh 7:
Set-ExecutionPolicy Bypass -Scope Process -Force
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
If you are already in pwsh 7, shell out to PS 5.1 in one line:
& "$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -Command "Set-ExecutionPolicy Bypass -Scope Process -Force; [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))"
2. Python: "Python not found after install"
The installer succeeded, but the new install path isn't in the current shell's $env:Path. Common locations: C:\Python312 (python.org InstallAllUsers=1), C:\Program Files\Python312 (winget system scope), $env:LOCALAPPDATA\Programs\Python\Python312 (winget per-user, default). To refresh PATH in the current session:
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
Or skip winget and Chocolatey entirely and install Python 3.12 directly from python.org:
$v = '3.12.13'
$u = "https://www.python.org/ftp/python/$v/python-$v-amd64.exe"
$f = "$env:TEMP\python-$v-amd64.exe"
Invoke-WebRequest $u -OutFile $f -UseBasicParsing
Start-Process $f -ArgumentList '/quiet','InstallAllUsers=1','PrependPath=1' -Wait
What the scripts set up — and why
These aren't just "install the CLI" scripts anymore. Each one stands up a full AI coding workbench: the terminals, the agentic frameworks, a document/media conversion toolkit, power utilities, and the MCP plumbing that lets Claude Code reach your files and the web. Here's every layer and the reasoning behind it.
Three editions, not one
There's a dedicated script per OS because the realities are genuinely different:
- macOS uses Homebrew, which refuses to run as root — so the macOS script never uses
sudo. macOS also defaults to zsh (not bash) and ships an ancient bash 3.2, so that script is written to be zsh-profile aware and 3.2-safe, and it detects Apple Silicon vs Intel (/opt/homebrewvs/usr/local). - Linux installs through
apt/dnfand does run withsudo. - Windows uses winget/Chocolatey under PowerShell.
Trying to force one file to do all three meant either breaking macOS (its bash can't run the Linux script's associative arrays) or weakening the Linux path. Separate scripts keep each one correct.
The four AI terminals
| Tool | Best at | Auth |
|---|---|---|
| Claude Code | deep, multi-file, agentic coding inside a repo | browser login or ANTHROPIC_API_KEY |
| OpenAI Codex CLI | OpenAI-model agentic coding in the terminal | ChatGPT login or OPENAI_API_KEY |
| ChatGPT CLI | quick questions, one-shot generations | OPENAI_API_KEY |
| GitHub Copilot CLI | shell-command suggestions, git help | gh auth login |
Codex is now installed by the Bash editions too (it was already covered on Windows), so all four are one selection away.
Optional: Kimi (Moonshot AI) — opt-in, off by default
The four terminals above run US-hosted models. There's a fifth worth knowing about when cost is the binding constraint: Kimi, from Moonshot AI. It is deliberately not installed by default. Pass --with-kimi (PowerShell: -WithKimi) and the script adds the Kimi Code CLI and prints the wiring notes below; leave the flag off and nothing about your stack changes — it stays US-only.
Why you'd reach for it. The current flagship (kimi-k2.7-code) is a trillion-parameter mixture-of-experts model (≈32B active per token) with a 256K context window, tuned for long-horizon agentic tool-use — the exact profile for a terminal agent grinding through a 40-item audit fix list without losing the thread. The original Kimi K2 scored 65.8 on SWE-bench Verified, in the same class as frontier US models at the time. The real draw is price: roughly $0.95 per million input tokens and $4.00 per million output as of this update, an order of magnitude cheaper on output than frontier models, with steep cache-hit discounts that compound in agentic loops resending the same context.
How to use it — two ways, and you don't have to pick just one:
-
As a drop-in inside Claude Code. Keep all your Claude Code muscle memory and point it at Kimi through Moonshot's Anthropic-compatible endpoint:
export ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic export ANTHROPIC_AUTH_TOKEN=your_moonshot_key # from platform.kimi.ai export ANTHROPIC_MODEL=kimi-k2.7-codeLeave those unset and Claude Code stays on Anthropic models. The tidy middle path: set only
CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.7-codeso your subagents fan out cheap on Kimi while the main thread keeps Claude's judgment. (The multi-model routing post has the full when-to-switch logic.) -
As its own CLI.
--with-kimiinstallskimi(Kimi Code). Grab a key at platform.kimi.ai — it needs a $1 activation top-up, and the free starter tier is only 3 requests/min, so budget the $10 tier for real agentic work — then runkimiand/login.
Why it's opt-in. Moonshot is a Chinese company, and both API endpoints (api.moonshot.ai and api.moonshot.cn) are China-operated with no published Western data-residency or zero-retention guarantee. For an agency touching client sites that's a real line to draw: route bulk, non-sensitive grunt work to Kimi for the price, but keep confidential client material on a US model or on self-hosted open weights. Want US-only? Just don't pass the flag. The lineup also moves fast (K2 → K2.6 → K2.7 Code inside a year), so check platform.kimi.ai for the current flagship model id before you pin one.
AI frameworks — the agentic + code-gen layer
- aider — an AI pair programmer that edits code directly in your git repo and commits as it goes.
- llm (Simon Willison) — a pluggable one-shot/
-cLLM CLI with a large plugin ecosystem; great for scripting prompts into pipelines. - repomix — packs an entire repository into a single, token-efficient file you can hand to any model for whole-repo context.
- files-to-prompt — turns a folder of files into one clean prompt.
- aichat — an all-in-one Rust LLM CLI (chat, REPL, RAG, shell assistant) that talks to many providers.
The conversion toolkit — feed anything to a model
Models read text. Most of what you want to ask about isn't text yet — it's a PDF, a scan, a slide deck, a video. This layer fixes that:
- markitdown (Microsoft) — converts Office files, PDFs, images and more into clean Markdown for LLM ingestion.
- poppler (
pdftotext) — pulls text straight out of PDFs. - tesseract — OCR; turns images and scans into text.
- pandoc — converts between just about any document formats (Markdown ↔ docx ↔ HTML ↔ PDF…).
- yt-dlp + ffmpeg — pull and transcode audio/video (pair with a transcription model).
- jc — converts the output of common CLI tools into JSON, so an AI (or
jq) can parse it.
Power utilities
- httpie (the command is
http) — a human-friendly HTTP client for poking at APIs. - ast-grep — structural, AST-aware code search and rewrite; far more precise than regex for AI-assisted refactors.
- gron — flattens JSON into greppable lines (and back with
gron -u). - tldr — example-first, community man pages.
- just — a make-style task runner for saving and re-running project commands.
…on top of the originals: ripgrep, jq, fd, bat, fzf, yq, tree, git-delta, shellcheck, tmux, sqlite3, ImageMagick, GhostScript, and (macOS) the GNU userland.
Deploy targets — Netlify, Cloudflare, Vercel
Each script installs Netlify CLI and Cloudflare Wrangler (with Vercel CLI optional), so you can push a site live from the terminal on whichever host you prefer — including the same edge platform this site's contingency plan already favors:
- Netlify:
netlify deploy --prod - Cloudflare Pages (static sites):
wrangler pages deploy ./dist --project-name=my-site— andwrangler deployfor Workers (edge functions) - Vercel:
vercel --prod
Auth is a one-time browser login (netlify login / wrangler login / vercel login); for CI/headless, Cloudflare uses CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID. Wrangler is npm install -g wrangler, so it stays current the same way every other tool here does — no pinned version.
Auto-deploy Cloudflare Pages on every git push
wrangler pages deploy is the manual one-shot. To make it automatic — every push to main builds and ships to Cloudflare Pages — drop this GitHub Actions workflow in your repo at .github/workflows/deploy.yml. It uses Cloudflare's official wrangler-action:
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
# Build your site — swap these for your own build command + output dir
- run: npm ci && npm run build
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy ./dist --project-name=my-site
Two one-time setup steps:
- Create the Pages project once —
wrangler pages project create my-site(or just runwrangler pages deploy ./dist --project-name=my-sitelocally once; the first deploy creates it). The--project-namein the workflow must match. - Add two repo secrets — in GitHub: Settings → Secrets and variables → Actions. Create
CLOUDFLARE_API_TOKEN(Cloudflare dashboard → My Profile → API Tokens, use the "Edit Cloudflare Workers" / Pages template) andCLOUDFLARE_ACCOUNT_ID(shown on the Workers & Pages overview). GitHub encrypts them andwrangler-actionkeeps them out of the logs.
That's it — push to main and the Action builds and deploys. Pull requests get their own preview deployments automatically.
No workflow file at all? Cloudflare Pages can also auto-build straight from Git: in the dashboard, Workers & Pages → Create → Pages → Connect to Git, pick the repo, set the build command and output directory, and every push deploys with zero YAML. The Action route above is the better fit when you want the build to run in your CI (custom steps, tests, monorepo control) before the deploy.
"Install everything" mode
Each script now opens with an install-mode choice: pick Everything to install the full recommended stack unattended, or Choose to be asked per component. You can skip the prompt entirely with --all (macOS/Linux) / -All (Windows) — the same as --auto/-AutoMode. API keys, Ollama, and LibreOffice stay opt-in even in "everything" mode, so nothing heavyweight or secret-bearing happens without you asking for it.
MCP servers + aligned repos
Model Context Protocol servers are how Claude Code reaches beyond the chat — your filesystem, the web, a browser, persistent memory. The scripts offer to register a current, maintained set with claude mcp add --scope user:
- filesystem, memory, sequential-thinking (Node, via
npx) - fetch, time (Python, via
uvx— installed only ifuvis present) - playwright (Microsoft's browser-automation server)
The reference servers are a mix of Node and Python runtimes —
fetch/timehave no npm package and must run viauvx. The scripts handle that split for you. (The old SQLite and Puppeteer reference servers were archived, so they're deliberately not included.)
The scripts also surface — and optionally git clone into ~/ai-tools — a curated set of aligned, actively-maintained repos: awesome-claude-code, awesome-mcp-servers, the official MCP reference servers, OpenAI Codex, and Aider.
Why Gemini CLI is opt-in
Google announced it is sunsetting Gemini CLI around 2026-06-18 (its Homebrew formula is already deprecated). Rather than install a tool that breaks days later, the scripts leave it reachable behind an explicit, default-no prompt with a warning, instead of bundling it into "everything" mode.
Staying current without editing the script
This was a deliberate design goal: the scripts pin no framework versions. Every AI tool is installed with a floating selector that always resolves to the latest published release:
- npm tools (Codex, repomix, ast-grep, tldr…) →
npm install -g <pkg>(no version) - pip/pipx tools (aider, llm, markitdown, httpie, files-to-prompt…) → latest on install, and the scripts run
pipx upgrade-allto refresh - Homebrew / apt / dnf / winget / choco → the package manager's current version
- MCP servers →
npx -y/uvxfetch the latest each run
So when aider or Codex ships a new release next week, you don't touch the script — re-run it (or run npm update -g, pipx upgrade-all, brew upgrade) and you're current. The only deliberate version reference anywhere is the Node LTS major (NODE_MAJOR, currently 22) — a once-a-year stability knob, not a per-release pin.
Full Linux script
Expand start-ai-terminal-kickstart.sh — Linux / WSL edition (apt / dnf)
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════════════
# AI Terminal Kickstart - Linux Edition
# Supports: Ubuntu 20.04+, Linux Mint 20+, RHEL 8+/9+, Fedora, AlmaLinux
#
# One script to prep a Linux PC for AI-powered terminal work:
# 1. Claude Code (Anthropic)
# 2. ChatGPT CLI (OpenAI)
# 3. GitHub Copilot CLI (GitHub)
# + Netlify CLI for direct site deployment
#
# Usage:
# chmod +x start-ai-terminal-kickstart.sh
# sudo ./start-ai-terminal-kickstart.sh
# sudo ./start-ai-terminal-kickstart.sh --auto # unattended mode
# sudo ./start-ai-terminal-kickstart.sh --with-kimi # opt-in: also install Kimi Code CLI
#
# Default is US-only models. Kimi (Moonshot AI) is China-operated and is NEVER
# installed unless you explicitly pass --with-kimi. It is intentionally skipped
# even under --auto/--all/--everything.
#
# Author : AI Terminal ops
# Date : 2026-03-25
# ═══════════════════════════════════════════════════════════════════════════════
set -uo pipefail
# NOTE: -e (errexit) is intentionally omitted. This script uses manual error
# handling with || true and explicit checks. set -e causes false exits on
# arithmetic operations returning 0 and optional package installs failing.
# ─── Configuration ────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMP_DIR="/tmp/ai-kickstart-$(date +%Y%m%d)"
LOG_FILE="$TEMP_DIR/kickstart.log"
AUTO_MODE=false
WITH_KIMI=false # opt-in only. Kimi (Moonshot AI) is China-operated; US-only is the default.
ACTUAL_USER="${SUDO_USER:-$USER}"
ACTUAL_HOME=$(eval echo "~$ACTUAL_USER")
# Parse arguments
for arg in "$@"; do
case "$arg" in
--auto|-a|--all|--everything) AUTO_MODE=true ;;
--with-kimi) WITH_KIMI=true ;;
--help|-h)
echo "Usage: sudo $0 [--auto|--all] [--with-kimi]"
echo " --auto, --all Install the full recommended stack without prompts"
echo " --with-kimi Opt-in: also install the Kimi Code CLI (Moonshot AI)."
echo " Default is US-only models; Kimi is China-operated and is"
echo " NEVER installed without this flag (skipped even under --auto)."
exit 0
;;
esac
done
mkdir -p "$TEMP_DIR"
touch "$LOG_FILE"
# ─── Detect Distro ───────────────────────────────────────────────────────────
DISTRO="unknown"
PKG_MGR="unknown"
PKG_INSTALL=""
PKG_UPDATE=""
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|linuxmint|pop|elementary|zorin)
DISTRO="debian"
PKG_MGR="apt"
PKG_INSTALL="apt-get install -y"
PKG_UPDATE="apt-get update -qq"
;;
rhel|centos|rocky|alma|ol)
DISTRO="rhel"
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
;;
fedora)
DISTRO="rhel"
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
;;
*)
# Try to detect by package manager
if command -v apt-get &>/dev/null; then
DISTRO="debian"
PKG_MGR="apt"
PKG_INSTALL="apt-get install -y"
PKG_UPDATE="apt-get update -qq"
elif command -v dnf &>/dev/null; then
DISTRO="rhel"
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
elif command -v yum &>/dev/null; then
DISTRO="rhel"
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update || true"
fi
;;
esac
fi
# ─── Helper Functions ────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
WHITE='\033[1;37m'
GRAY='\033[0;37m'
NC='\033[0m' # No Color
log() { echo "[$(date +%H:%M:%S)] $*" >> "$LOG_FILE"; }
section() { echo -e "\n ${CYAN}=================================================================${NC}"; echo -e " ${CYAN}$1${NC}"; echo -e " ${CYAN}=================================================================${NC}"; log "SECTION: $1"; }
step() { echo -e "\n ${CYAN}>> $1${NC}"; log "STEP: $1"; }
ok() { echo -e " ${GREEN}[OK]${NC} $1"; log " OK: $1"; }
warn() { echo -e " ${YELLOW}[WARN]${NC} $1"; log "WARN: $1"; }
fail() { echo -e " ${RED}[FAIL]${NC} $1"; log "FAIL: $1"; }
info() { echo -e " ${GRAY}$1${NC}"; }
tip() { echo -e " ${YELLOW}[TIP]${NC} $1"; }
cmd_exists() { command -v "$1" &>/dev/null; }
# ─── Progress bar ────────────────────────────────────────────────────────────
TOTAL_PHASES=6
CURRENT_PHASE=0
show_progress() {
local label="$1"
CURRENT_PHASE=$((CURRENT_PHASE + 1))
local pct=$((CURRENT_PHASE * 100 / TOTAL_PHASES))
local bar_len=30
local filled=$((bar_len * CURRENT_PHASE / TOTAL_PHASES))
local empty=$((bar_len - filled))
local bar=$(printf '%0.s█' $(seq 1 $filled 2>/dev/null) || true)
local space=$(printf '%0.s░' $(seq 1 $empty 2>/dev/null) || true)
printf "\r ${CYAN}[%s%s] %3d%% - Phase %d/%d: %s${NC} \n" \
"$bar" "$space" "$pct" "$CURRENT_PHASE" "$TOTAL_PHASES" "$label"
}
# Spinner for long-running commands
run_with_spinner() {
local label="$1"
shift
local cmd="$*"
local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
local i=0
eval "$cmd" &
local pid=$!
while kill -0 "$pid" 2>/dev/null; do
local c="${spin:i++%${#spin}:1}"
printf "\r ${CYAN}%s${NC} %s " "$c" "$label"
sleep 0.15
done
wait "$pid"
local rc=$?
printf "\r \r"
return $rc
}
# ─── Version Configuration (change these to update targets) ──────────────────
NODE_MAJOR="22" # Node.js LTS major — the ONLY pinned version; all AI tools below install latest
# Python version is auto-detected from system package manager
ask_yn() {
local prompt="$1"
local default="${2:-y}"
if $AUTO_MODE; then
[[ "$default" == "y" ]] && return 0 || return 1
fi
local hint="Y/n"
[[ "$default" == "n" ]] && hint="y/N"
echo -ne " ${YELLOW}$prompt ($hint): ${NC}"
read -r answer
answer="${answer:-$default}"
[[ "$answer" =~ ^[Yy] ]]
}
run_as_user() {
# Run a command as the actual user (not root)
# Uses "$@" quoting pattern via a helper to preserve spaces in arguments
if [ "$EUID" -eq 0 ] && [ -n "${SUDO_USER:-}" ]; then
sudo -u "$SUDO_USER" -- bash -c "$1"
else
bash -c "$1"
fi
}
# Track results for final summary
declare -a RESULT_NAMES=()
declare -a RESULT_STATUS=()
declare -a RESULT_DETAIL=()
add_result() {
RESULT_NAMES+=("$1")
RESULT_STATUS+=("$2")
RESULT_DETAIL+=("${3:-}")
}
# ─── Banner ───────────────────────────────────────────────────────────────────
echo -e "${MAGENTA}"
cat << 'BANNER'
╔═══════════════════════════════════════════════════════════════════╗
║ ║
║ A I T E R M I N A L K I C K S T A R T ║
║ ───────────────────────────────────────── ║
║ Linux Edition (Ubuntu / RHEL / Mint) ║
║ ║
║ Claude Code | ChatGPT CLI | GitHub Copilot CLI ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝
BANNER
echo -e "${NC}"
# ─── System Info ──────────────────────────────────────────────────────────────
echo -e " ${WHITE}System Info:${NC}"
echo -e " ${GRAY}OS : $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d '"')${NC}"
echo -e " ${GRAY}Kernel : $(uname -r)${NC}"
echo -e " ${GRAY}Distro Type : $DISTRO ($PKG_MGR)${NC}"
echo -e " ${GRAY}User : $ACTUAL_USER${NC}"
echo -e " ${GRAY}Bash : $BASH_VERSION${NC}"
echo -e " ${GRAY}Log File : $LOG_FILE${NC}"
echo ""
if [ "$EUID" -ne 0 ]; then
echo -e " ${YELLOW}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${YELLOW}│ WARNING: Not running as root. Re-run with sudo: │${NC}"
echo -e " ${YELLOW}│ sudo $0${NC}"
echo -e " ${YELLOW}└──────────────────────────────────────────────────────────────┘${NC}"
echo ""
fi
if [ "$DISTRO" = "unknown" ]; then
fail "Could not detect your Linux distribution."
echo -e " Supported: Ubuntu, Linux Mint, RHEL, CentOS, Rocky, Alma, Fedora"
exit 1
fi
# ─── Install mode (Everything vs Choose) ─────────────────────────────────────
if ! $AUTO_MODE; then
echo -e " ${WHITE}Install mode:${NC}"
echo -e " ${GRAY} 1) Everything — install the full recommended stack, unattended${NC}"
echo -e " ${GRAY} 2) Choose — ask before each component${NC}"
echo -ne " ${YELLOW}Pick [1/2] (default 2): ${NC}"
read -r INSTALL_MODE
if [ "${INSTALL_MODE:-2}" = "1" ]; then
AUTO_MODE=true
info "Mode: install everything (unattended)."
else
info "Mode: choose each component."
fi
echo ""
fi
# ─── Pre-flight: Admin, Network, Disk, Repos, TLS ────────────────────────────
show_progress "Pre-flight Checks"
section "PRE-FLIGHT CHECKS"
# Log session separator
echo "" >> "$LOG_FILE"
echo "======================================================================" >> "$LOG_FILE"
echo "Session started: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE"
echo "OS: $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d '"')" >> "$LOG_FILE"
echo "Distro type: $DISTRO ($PKG_MGR) | User: $ACTUAL_USER | EUID: $EUID" >> "$LOG_FILE"
echo "======================================================================" >> "$LOG_FILE"
# Admin / root access check
step "Checking root/sudo privileges"
if [ "$EUID" -eq 0 ]; then
ok "Running as root - full install capability"
add_result "Root Access" "OK" "Elevated"
else
warn "NOT running as root. Package installs WILL fail."
info "Re-run with: sudo $0"
info "The following require root: apt/dnf install, repo setup, locale changes"
add_result "Root Access" "FAILED" "Not root"
fi
# TLS / certificates check
step "Checking TLS certificates"
if [ -f /etc/ssl/certs/ca-certificates.crt ] || [ -d /etc/pki/tls/certs ]; then
ok "CA certificate bundle present"
else
warn "CA certificates may be missing - HTTPS downloads could fail"
if [ "$DISTRO" = "debian" ]; then
$PKG_INSTALL ca-certificates >> "$LOG_FILE" 2>&1 || true
elif [ "$DISTRO" = "rhel" ]; then
$PKG_INSTALL ca-certificates >> "$LOG_FILE" 2>&1 || true
fi
fi
# OpenSSL version check
if cmd_exists openssl; then
SSL_VER=$(openssl version 2>/dev/null || true)
ok "OpenSSL: $SSL_VER"
else
warn "openssl not found - some tools may have TLS issues"
fi
add_result "TLS/Certs" "OK" ""
# Network connectivity - test multiple download sources
step "Testing internet connectivity to download sources"
REACHABLE=0
UNREACHABLE=0
declare -A NET_ENDPOINTS=(
["GitHub API"]="https://api.github.com"
["NodeSource"]="https://deb.nodesource.com"
["Claude installer"]="https://claude.ai"
["npm registry"]="https://registry.npmjs.org"
["PyPI"]="https://pypi.org"
)
for name in "${!NET_ENDPOINTS[@]}"; do
url="${NET_ENDPOINTS[$name]}"
if curl -sf --connect-timeout 8 --max-time 12 -o /dev/null "$url" 2>/dev/null; then
ok "$name reachable"
((REACHABLE++)) || true
else
warn "$name ($url) - UNREACHABLE"
((UNREACHABLE++)) || true
fi
done
TOTAL_EP=$((REACHABLE + UNREACHABLE))
if [ "$UNREACHABLE" -eq 0 ]; then
ok "All $REACHABLE download sources reachable"
add_result "Network" "OK" "$REACHABLE/$TOTAL_EP reachable"
elif [ "$UNREACHABLE" -lt 3 ]; then
warn "$UNREACHABLE of $TOTAL_EP sources unreachable. Some installs may fail."
add_result "Network" "PARTIAL" "$REACHABLE/$TOTAL_EP reachable"
else
fail "Most download sources unreachable. Script will likely fail."
info "Check: DNS, proxy, firewall, or VPN settings."
add_result "Network" "FAILED" "$REACHABLE/$TOTAL_EP reachable"
fi
# Disk space check
step "Checking available disk space"
FREE_GB=$(df -BG / 2>/dev/null | awk 'NR==2{print $4}' | tr -d 'G')
TOTAL_GB=$(df -BG / 2>/dev/null | awk 'NR==2{print $2}' | tr -d 'G')
if [ "${FREE_GB:-0}" -lt 2 ]; then
fail "CRITICAL: Only ${FREE_GB}GB free on / (${TOTAL_GB}GB total). Need at least 2GB."
info "Free up disk space before continuing."
add_result "Disk Space" "FAILED" "${FREE_GB}GB free"
elif [ "${FREE_GB:-0}" -lt 5 ]; then
warn "Low: ${FREE_GB}GB free on / (${TOTAL_GB}GB total). Recommend 5+ GB."
add_result "Disk Space" "WARN" "${FREE_GB}GB free"
else
ok "Disk space: ${FREE_GB}GB free on / (${TOTAL_GB}GB total)"
add_result "Disk Space" "OK" "${FREE_GB}GB free"
fi
# Check for package manager lock files
step "Checking for package manager locks"
if [ "$DISTRO" = "debian" ]; then
if fuser /var/lib/dpkg/lock-frontend &>/dev/null 2>&1 || fuser /var/lib/apt/lists/lock &>/dev/null 2>&1; then
warn "apt/dpkg is locked by another process. Wait for it to finish."
info "Run: sudo lsof /var/lib/dpkg/lock-frontend to see what holds it."
add_result "Package Lock" "WARN" "apt locked"
else
ok "No package manager locks detected"
add_result "Package Lock" "OK" ""
fi
elif [ "$DISTRO" = "rhel" ]; then
if [ -f /var/run/yum.pid ] || [ -f /var/run/dnf.pid ]; then
warn "dnf/yum lock file found. Another install may be running."
add_result "Package Lock" "WARN" "dnf locked"
else
ok "No package manager locks detected"
add_result "Package Lock" "OK" ""
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 1: SYSTEM UPDATE & CORE PACKAGES
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "System Update & Core Packages"
section "PHASE 1: System Update & Core Packages"
step "Updating package lists"
eval "$PKG_UPDATE" >> "$LOG_FILE" 2>&1 || true
ok "Package lists updated"
# ─── Essential build tools ────────────────────────────────────────────────────
step "Installing essential build tools"
if [ "$DISTRO" = "debian" ]; then
$PKG_INSTALL build-essential curl wget ca-certificates gnupg lsb-release \
software-properties-common unzip tar >> "$LOG_FILE" 2>&1 || true
elif [ "$DISTRO" = "rhel" ]; then
$PKG_INSTALL gcc gcc-c++ make curl wget ca-certificates gnupg2 unzip tar \
>> "$LOG_FILE" 2>&1 || true
# Enable EPEL for extra packages
if ! rpm -q epel-release &>/dev/null; then
$PKG_INSTALL epel-release >> "$LOG_FILE" 2>&1 || true
fi
fi
ok "Build tools installed"
add_result "Build Tools" "INSTALLED" ""
step "Checking locale and UTF-8 support"
CURRENT_LANG="${LANG:-}"
if [[ "$CURRENT_LANG" == *"UTF-8"* ]] || [[ "$CURRENT_LANG" == *"utf8"* ]]; then
ok "Locale is UTF-8: $CURRENT_LANG"
else
warn "Locale is '$CURRENT_LANG' - may cause display issues with AI terminal output"
if [ "$DISTRO" = "debian" ]; then
$PKG_INSTALL locales >> "$LOG_FILE" 2>&1 || true
locale-gen en_US.UTF-8 >> "$LOG_FILE" 2>&1 || true
update-locale LANG=en_US.UTF-8 >> "$LOG_FILE" 2>&1 || true
fi
tip "Add to ~/.bashrc: export LANG=en_US.UTF-8"
if ! grep -q 'LANG=en_US.UTF-8' "$ACTUAL_HOME/.bashrc" 2>/dev/null; then
echo 'export LANG=en_US.UTF-8' >> "$ACTUAL_HOME/.bashrc"
fi
fi
# ─── Git ──────────────────────────────────────────────────────────────────────
step "Checking Git"
if cmd_exists git; then
ok "Git already installed ($(git --version))"
add_result "Git" "PRESENT" "$(git --version)"
else
info "Git is REQUIRED for Claude Code and version control."
if ask_yn "Install Git?"; then
$PKG_INSTALL git >> "$LOG_FILE" 2>&1 || true
ok "Git installed ($(git --version))"
add_result "Git" "INSTALLED" "$(git --version)"
else
warn "Skipping Git - Claude Code WILL NOT WORK without it!"
add_result "Git" "SKIPPED" "WARNING"
fi
fi
# Git user config check
if cmd_exists git; then
step "Checking Git configuration"
GIT_USER=$(run_as_user "git config --global user.name" 2>/dev/null)
GIT_EMAIL=$(run_as_user "git config --global user.email" 2>/dev/null)
if [ -z "$GIT_USER" ] || [ -z "$GIT_EMAIL" ]; then
warn "Git user.name or user.email not set. Commits will fail without these."
tip "Run: git config --global user.name 'Your Name'"
tip "Run: git config --global user.email 'you@example.com'"
else
ok "Git user: $GIT_USER <$GIT_EMAIL>"
fi
run_as_user "git config --global init.defaultBranch main" 2>/dev/null
run_as_user "git config --global core.autocrlf input" 2>/dev/null
fi
# ─── Node.js (via NodeSource) ────────────────────────────────────────────────
step "Checking Node.js"
if cmd_exists node; then
ok "Node.js already installed ($(node --version))"
cmd_exists npm && ok "npm $(npm --version)"
cmd_exists npx && ok "npx available"
add_result "Node.js" "PRESENT" "$(node --version)"
else
info "Node.js is required for Claude Code, npm tools, and MCP servers."
if ask_yn "Install Node.js ${NODE_MAJOR} LTS?"; then
if [ "$DISTRO" = "debian" ]; then
# NodeSource setup for Debian/Ubuntu
curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - >> "$LOG_FILE" 2>&1 || true
$PKG_INSTALL nodejs >> "$LOG_FILE" 2>&1 || true
elif [ "$DISTRO" = "rhel" ]; then
curl -fsSL https://rpm.nodesource.com/setup_${NODE_MAJOR}.x | bash - >> "$LOG_FILE" 2>&1 || true
$PKG_INSTALL nodejs >> "$LOG_FILE" 2>&1 || true
fi
if cmd_exists node; then
ok "Node.js $(node --version) installed"
ok "npm $(npm --version)"
add_result "Node.js" "INSTALLED" "$(node --version)"
else
fail "Node.js installation failed"
add_result "Node.js" "FAILED" ""
fi
else
add_result "Node.js" "SKIPPED" ""
fi
fi
# Fix npm global permissions for the actual user
if cmd_exists npm; then
step "Configuring npm global directory (no-sudo installs)"
NPM_GLOBAL="$ACTUAL_HOME/.npm-global"
run_as_user "mkdir -p '$NPM_GLOBAL'"
run_as_user "npm config set prefix '$NPM_GLOBAL'"
# Add to PATH in bashrc if not already there
BASHRC="$ACTUAL_HOME/.bashrc"
if ! grep -q 'npm-global' "$BASHRC" 2>/dev/null; then
echo '' >> "$BASHRC"
echo '# npm global packages (no sudo needed)' >> "$BASHRC"
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "$BASHRC"
fi
export PATH="$NPM_GLOBAL/bin:$PATH"
ok "npm global dir: $NPM_GLOBAL"
fi
# ─── Python ───────────────────────────────────────────────────────────────────
step "Checking Python"
PYTHON_CMD=""
if cmd_exists python3; then
PYTHON_CMD="python3"
elif cmd_exists python; then
PYTHON_CMD="python"
fi
if [ -n "$PYTHON_CMD" ]; then
ok "Python already installed ($($PYTHON_CMD --version 2>&1))"
add_result "Python" "PRESENT" "$($PYTHON_CMD --version 2>&1)"
else
info "Python is used for MCP servers, AI SDKs, and data processing."
if ask_yn "Install Python 3?"; then
if [ "$DISTRO" = "debian" ]; then
$PKG_INSTALL python3 python3-pip python3-venv python3-dev >> "$LOG_FILE" 2>&1 || true
elif [ "$DISTRO" = "rhel" ]; then
$PKG_INSTALL python3 python3-pip python3-devel >> "$LOG_FILE" 2>&1 || true
fi
PYTHON_CMD="python3"
if cmd_exists python3; then
ok "Python $(python3 --version 2>&1) installed"
add_result "Python" "INSTALLED" "$(python3 --version 2>&1)"
else
fail "Python installation failed"
add_result "Python" "FAILED" ""
fi
else
add_result "Python" "SKIPPED" ""
fi
fi
# Upgrade pip
if [ -n "$PYTHON_CMD" ] && cmd_exists "$PYTHON_CMD"; then
step "Upgrading pip"
run_as_user "$PYTHON_CMD -m pip install --upgrade pip --quiet" 2>/dev/null || true
ok "pip upgraded"
fi
# ─── CLI Utilities ────────────────────────────────────────────────────────────
step "Installing CLI utilities"
declare -A CLI_TOOLS
if [ "$DISTRO" = "debian" ]; then
CLI_TOOLS=(
[ripgrep]="ripgrep"
[jq]="jq"
[fd]="fd-find"
[imagemagick]="imagemagick"
[p7zip]="p7zip-full"
[ghostscript]="ghostscript"
[ffmpeg]="ffmpeg"
[bat]="bat"
[fzf]="fzf"
[yq]="yq"
[tree]="tree"
[delta]="git-delta"
[shellcheck]="shellcheck"
[tmux]="tmux"
[sqlite3]="sqlite3"
)
elif [ "$DISTRO" = "rhel" ]; then
CLI_TOOLS=(
[ripgrep]="ripgrep"
[jq]="jq"
[fd]="fd-find"
[imagemagick]="ImageMagick"
[p7zip]="p7zip"
[ghostscript]="ghostscript"
[ffmpeg]="ffmpeg"
[bat]="bat"
[fzf]="fzf"
[yq]="yq"
[tree]="tree"
[delta]="git-delta"
[shellcheck]="ShellCheck"
[tmux]="tmux"
[sqlite3]="sqlite"
)
fi
CLI_CMDS=(
[ripgrep]="rg"
[jq]="jq"
[fd]="fdfind"
[imagemagick]="convert"
[p7zip]="7z"
[ghostscript]="gs"
[ffmpeg]="ffmpeg"
[bat]="batcat"
[fzf]="fzf"
[yq]="yq"
[tree]="tree"
[delta]="delta"
[shellcheck]="shellcheck"
[tmux]="tmux"
[sqlite3]="sqlite3"
)
CLI_DESC=(
[ripgrep]="Ultra-fast code search (used by Claude Code)"
[jq]="JSON processor for API responses"
[fd]="Fast file finder"
[imagemagick]="Image processing (convert, resize)"
[p7zip]="Archive compression"
[ghostscript]="PDF/PostScript engine (required by ImageMagick for PDFs)"
[ffmpeg]="Video/audio processing and media conversion"
[bat]="Syntax-highlighted file viewer (better cat)"
[fzf]="Fuzzy finder for files, history, and commands"
[yq]="YAML processor (like jq but for YAML)"
[tree]="Directory structure viewer"
[delta]="Enhanced git diff viewer with syntax highlighting"
[shellcheck]="Shell script linter and validator"
[tmux]="Terminal multiplexer for persistent sessions"
[sqlite3]="Lightweight database engine and client"
)
for tool in ripgrep jq fd imagemagick p7zip ghostscript ffmpeg bat fzf yq tree delta shellcheck tmux sqlite3; do
pkg="${CLI_TOOLS[$tool]:-}"
cmd="${CLI_CMDS[$tool]:-$tool}"
desc="${CLI_DESC[$tool]:-}"
if cmd_exists "$cmd"; then
ok "$tool is installed"
add_result "$tool" "PRESENT" ""
else
info "$tool: $desc"
if ask_yn "Install $tool?"; then
$PKG_INSTALL "$pkg" >> "$LOG_FILE" 2>&1 || true
ok "$tool installed"
add_result "$tool" "INSTALLED" ""
else
add_result "$tool" "SKIPPED" ""
fi
fi
done
# ─── Python Packages ─────────────────────────────────────────────────────────
if [ -n "$PYTHON_CMD" ] && cmd_exists "$PYTHON_CMD"; then
step "Python packages for AI development"
info "These packages enhance what Claude Code and AI tools can do:"
info " Data: numpy, pandas, polars, matplotlib, openpyxl"
info " Web: requests, httpx, beautifulsoup4"
info " AI: anthropic, openai, fastmcp, mcp"
info " Dev: pydantic, rich, pyyaml, python-dotenv"
if ask_yn "Install recommended Python packages?"; then
PACKAGES="numpy pandas polars matplotlib openpyxl requests httpx beautifulsoup4 lxml anthropic openai fastmcp mcp pydantic rich pyyaml python-dotenv Pillow chardet tabulate jsonlines"
run_as_user "$PYTHON_CMD -m pip install --user --upgrade $PACKAGES --quiet" >> "$LOG_FILE" 2>&1 || true
ok "Python packages installed"
add_result "Python Packages" "INSTALLED" "22 packages"
else
add_result "Python Packages" "SKIPPED" ""
fi
# uv package manager
step "Checking uv (fast Python package manager)"
if cmd_exists uv; then
ok "uv already installed ($(uv --version 2>/dev/null))"
add_result "uv" "PRESENT" ""
else
if ask_yn "Install uv?"; then
run_as_user "curl -LsSf https://astral.sh/uv/install.sh | sh" >> "$LOG_FILE" 2>&1 || true
export PATH="$ACTUAL_HOME/.cargo/bin:$PATH"
ok "uv installed"
add_result "uv" "INSTALLED" ""
else
add_result "uv" "SKIPPED" ""
fi
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 2: AI TERMINAL INSTALLATIONS
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "AI Terminal Solutions"
section "PHASE 2: AI Terminal Solutions"
echo ""
echo -e " ${WHITE}Which AI terminal solutions would you like to set up?${NC}"
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ 1. Claude Code (Anthropic) │${NC}"
echo -e " ${GRAY}│ Best for: Deep code understanding, multi-file edits, │${NC}"
echo -e " ${GRAY}│ agentic coding, MCP servers, security research │${NC}"
echo -e " ${GRAY}│ Auth: Browser login (OAuth) or ANTHROPIC_API_KEY │${NC}"
echo -e " ${CYAN}│ │${NC}"
echo -e " ${CYAN}│ 2. ChatGPT CLI (OpenAI) via npx chatgpt │${NC}"
echo -e " ${GRAY}│ Best for: Quick questions, brainstorming, general AI │${NC}"
echo -e " ${GRAY}│ Auth: OPENAI_API_KEY environment variable │${NC}"
echo -e " ${CYAN}│ │${NC}"
echo -e " ${CYAN}│ 3. GitHub Copilot CLI (GitHub) │${NC}"
echo -e " ${GRAY}│ Best for: Shell command suggestions, git operations │${NC}"
echo -e " ${GRAY}│ Auth: gh auth login (browser-based) │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
echo ""
INSTALL_CLAUDE=false
INSTALL_CHATGPT=false
INSTALL_COPILOT=false
ask_yn "Install Claude Code?" && INSTALL_CLAUDE=true
ask_yn "Install ChatGPT CLI tools?" && INSTALL_CHATGPT=true
ask_yn "Install GitHub Copilot CLI?" && INSTALL_COPILOT=true
# ─── Claude Code ──────────────────────────────────────────────────────────────
if $INSTALL_CLAUDE; then
section "Installing Claude Code"
if cmd_exists claude; then
ok "Claude Code already installed ($(claude --version 2>/dev/null || echo 'installed'))"
add_result "Claude Code" "PRESENT" ""
else
step "Installing Claude Code via official installer..."
info "This uses Anthropic's official install script."
run_as_user "curl -fsSL https://claude.ai/install.sh | sh" >> "$LOG_FILE" 2>&1 || true
# Add to PATH
export PATH="$ACTUAL_HOME/.local/bin:$PATH"
if ! grep -qF '.local/bin' "$ACTUAL_HOME/.bashrc" 2>/dev/null; then
echo '' >> "$ACTUAL_HOME/.bashrc"
echo '# Claude Code PATH' >> "$ACTUAL_HOME/.bashrc"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$ACTUAL_HOME/.bashrc"
fi
if run_as_user "command -v claude" &>/dev/null; then
ok "Claude Code installed!"
add_result "Claude Code" "INSTALLED" ""
else
# Fallback: npm install
warn "Official installer may need a new shell. Trying npm fallback..."
if cmd_exists npm; then
run_as_user "npm install -g @anthropic-ai/claude-code" >> "$LOG_FILE" 2>&1 || true
if run_as_user "command -v claude" &>/dev/null; then
ok "Claude Code installed via npm"
add_result "Claude Code" "INSTALLED" "via npm"
else
fail "Claude Code install needs a terminal restart"
tip "Close this terminal, open a new one, and run: claude"
add_result "Claude Code" "INSTALLED" "restart terminal"
fi
else
fail "npm not available for fallback install"
add_result "Claude Code" "FAILED" "npm missing"
fi
fi
fi
# Claude Code authentication guide
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ CLAUDE CODE AUTHENTICATION │${NC}"
echo -e " ${CYAN}├──────────────────────────────────────────────────────────────┤${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${GRAY}│ Option A - Browser Login (easiest): │${NC}"
echo -e " ${WHITE}│ 1. Run: claude │${NC}"
echo -e " ${WHITE}│ 2. Browser opens -> log in at claude.ai │${NC}"
echo -e " ${WHITE}│ 3. Click Authorize -> return to terminal │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${GRAY}│ Option B - API Key: │${NC}"
echo -e " ${WHITE}│ 1. Get key from console.anthropic.com/settings/keys │${NC}"
echo -e " ${WHITE}│ 2. export ANTHROPIC_API_KEY=\"sk-ant-your-key\" │${NC}"
echo -e " ${WHITE}│ 3. Add to ~/.bashrc to persist │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
if ask_yn "Set ANTHROPIC_API_KEY now?" "n"; then
echo -ne " ${YELLOW}Enter your Anthropic API key (sk-ant-...): ${NC}"
read -r ANTHROPIC_KEY
if [[ "$ANTHROPIC_KEY" == sk-ant-* ]]; then
echo "export ANTHROPIC_API_KEY=\"$ANTHROPIC_KEY\"" >> "$ACTUAL_HOME/.bashrc"
export ANTHROPIC_API_KEY="$ANTHROPIC_KEY"
ok "ANTHROPIC_API_KEY saved to ~/.bashrc"
else
warn "Key doesn't start with sk-ant- . Skipped. Use browser login instead."
fi
fi
fi
# ─── ChatGPT CLI ─────────────────────────────────────────────────────────────
if $INSTALL_CHATGPT; then
section "Installing ChatGPT CLI Tools"
step "Installing OpenAI-compatible CLI tools..."
# Install the Node.js based chatgpt client
if cmd_exists npm; then
info "Installing chatgpt npm package..."
run_as_user "npm install -g chatgpt-cli" >> "$LOG_FILE" 2>&1 || true
ok "chatgpt-cli installed (run: chatgpt)"
add_result "ChatGPT CLI" "INSTALLED" "chatgpt-cli"
else
warn "npm not available - cannot install ChatGPT CLI"
add_result "ChatGPT CLI" "FAILED" "npm missing"
fi
# Install the Python openai package for scripting
if [ -n "${PYTHON_CMD:-}" ] && cmd_exists "$PYTHON_CMD"; then
run_as_user "$PYTHON_CMD -m pip install --user openai --quiet" >> "$LOG_FILE" 2>&1 || true
ok "OpenAI Python SDK installed"
fi
# Authentication guide
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ CHATGPT AUTHENTICATION │${NC}"
echo -e " ${CYAN}├──────────────────────────────────────────────────────────────┤${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${WHITE}│ 1. Get API key: https://platform.openai.com/api-keys │${NC}"
echo -e " ${WHITE}│ 2. Set it: export OPENAI_API_KEY=\"sk-your-key\" │${NC}"
echo -e " ${WHITE}│ 3. Persist: add the export line to ~/.bashrc │${NC}"
echo -e " ${WHITE}│ 4. Test: curl https://api.openai.com/v1/models \\ │${NC}"
echo -e " ${WHITE}│ -H \"Authorization: Bearer \$OPENAI_API_KEY\" │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
if ask_yn "Set OPENAI_API_KEY now?" "n"; then
echo -ne " ${YELLOW}Enter your OpenAI API key (sk-...): ${NC}"
read -r OPENAI_KEY
if [[ "$OPENAI_KEY" == sk-* ]]; then
echo "export OPENAI_API_KEY=\"$OPENAI_KEY\"" >> "$ACTUAL_HOME/.bashrc"
export OPENAI_API_KEY="$OPENAI_KEY"
ok "OPENAI_API_KEY saved to ~/.bashrc"
else
warn "Key format not recognized. Set it manually later."
fi
fi
fi
# ─── GitHub Copilot CLI ──────────────────────────────────────────────────────
if $INSTALL_COPILOT; then
section "Installing GitHub Copilot CLI"
# Install GitHub CLI first
step "Checking GitHub CLI (gh)"
if cmd_exists gh; then
ok "GitHub CLI already installed ($(gh --version 2>/dev/null | head -1))"
else
info "GitHub CLI is required for Copilot CLI."
if ask_yn "Install GitHub CLI?" "y"; then
if [ "$DISTRO" = "debian" ]; then
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg 2>/dev/null
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg || true
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list
apt-get update -qq >> "$LOG_FILE" 2>&1 || true
$PKG_INSTALL gh >> "$LOG_FILE" 2>&1 || true
elif [ "$DISTRO" = "rhel" ]; then
dnf install -y 'dnf-command(config-manager)' >> "$LOG_FILE" 2>&1 || true
dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo >> "$LOG_FILE" 2>&1 || true
$PKG_INSTALL gh >> "$LOG_FILE" 2>&1 || true
fi
fi
fi
if cmd_exists gh; then
ok "GitHub CLI: $(gh --version 2>/dev/null | head -1)"
add_result "GitHub CLI" "PRESENT" ""
step "Installing Copilot CLI extension..."
run_as_user "gh extension install github/gh-copilot" >> "$LOG_FILE" 2>&1 || true
ok "Copilot CLI extension installed"
add_result "GitHub Copilot CLI" "INSTALLED" ""
# Authentication guide
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ GITHUB COPILOT AUTHENTICATION │${NC}"
echo -e " ${CYAN}├──────────────────────────────────────────────────────────────┤${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${WHITE}│ 1. Run: gh auth login │${NC}"
echo -e " ${WHITE}│ 2. Choose: GitHub.com │${NC}"
echo -e " ${WHITE}│ 3. Choose: Login with a web browser │${NC}"
echo -e " ${WHITE}│ 4. Copy the one-time code shown │${NC}"
echo -e " ${WHITE}│ 5. Browser opens -> paste code -> Authorize │${NC}"
echo -e " ${WHITE}│ 6. Return to terminal -> authenticated │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${GRAY}│ Requires: GitHub Copilot subscription (\$10/mo or free │${NC}"
echo -e " ${GRAY}│ for students and open source maintainers) │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
if ask_yn "Log in to GitHub now?" "n"; then
run_as_user "gh auth login"
fi
else
fail "GitHub CLI not available"
add_result "GitHub Copilot CLI" "FAILED" "gh CLI missing"
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 2.5: AI FRAMEWORKS, CODEX & CONVERSION CLIs
# ═══════════════════════════════════════════════════════════════════════════════
section "AI Frameworks, Codex & Conversion CLIs"
info "Agentic helpers (aider, llm), repo/document packers (repomix, files-to-prompt,"
info "markitdown) and converters (pandoc, poppler, tesseract OCR, yt-dlp, jc) that"
info "feed code and documents into any AI model."
# Make sure pipx / pip --user entry points (~/.local/bin) are on PATH
if ! grep -qF '.local/bin' "$ACTUAL_HOME/.bashrc" 2>/dev/null; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$ACTUAL_HOME/.bashrc"
fi
export PATH="$ACTUAL_HOME/.local/bin:$PATH"
# PEP 668-aware pip (Debian 12+/Fedora system Python is "externally managed")
ai_pip() {
run_as_user "$PYTHON_CMD -m pip install --user --upgrade $* --quiet" >> "$LOG_FILE" 2>&1 \
|| run_as_user "$PYTHON_CMD -m pip install --user --break-system-packages --upgrade $* --quiet" >> "$LOG_FILE" 2>&1 \
|| true
}
if ask_yn "Install the AI framework + conversion bundle?"; then
# ── Document/media conversion tools via the system package manager ──
step "Conversion tools (pandoc, poppler, tesseract, jc, glow, yt-dlp, gron, just)"
# Install one at a time. apt/dnf abort the ENTIRE batch if any single package
# is missing in the configured repos (e.g. 'just'/'gron' aren't in older
# Ubuntu/Debian or EL8), so a per-package loop lets the resolvable subset
# install instead of taking everything down with it.
if [ "$DISTRO" = "debian" ]; then
conv_pkgs="pandoc poppler-utils tesseract-ocr jc glow yt-dlp gron just"
elif [ "$DISTRO" = "rhel" ]; then
# jc / glow / yt-dlp / tesseract / gron are in EPEL on RHEL (enabled in Phase 1);
# 'just' is packaged as 'rust-just' on Fedora/RHEL.
conv_pkgs="pandoc poppler-utils tesseract jc glow yt-dlp gron rust-just"
else
conv_pkgs=""
fi
for cpkg in $conv_pkgs; do
$PKG_INSTALL "$cpkg" >> "$LOG_FILE" 2>&1 || true
done
for probe in "pandoc:pandoc" "pdftotext:poppler" "tesseract:tesseract" "jc:jc" "glow:glow" "yt-dlp:yt-dlp" "gron:gron" "just:just"; do
fw_bin="${probe%%:*}"; fw_name="${probe#*:}"
if cmd_exists "$fw_bin"; then
ok "$fw_name ($fw_bin) installed"; add_result "$fw_name" "INSTALLED" ""
else
warn "$fw_name ($fw_bin) not installed"; add_result "$fw_name" "SKIPPED" ""
fi
done
# ── Standalone Python AI CLIs via pipx (isolated, PEP 668-safe) ──
if [ -n "${PYTHON_CMD:-}" ] && cmd_exists "$PYTHON_CMD"; then
step "pipx + aider, llm, files-to-prompt, markitdown"
if ! cmd_exists pipx; then
$PKG_INSTALL pipx >> "$LOG_FILE" 2>&1 || ai_pip pipx
run_as_user "pipx ensurepath" >> "$LOG_FILE" 2>&1 || true
fi
if cmd_exists pipx; then
for app in aider-chat llm files-to-prompt "markitdown[all]" httpie; do
run_as_user "pipx install '$app'" >> "$LOG_FILE" 2>&1 \
|| run_as_user "pipx install --force '$app'" >> "$LOG_FILE" 2>&1 || true
done
# Refresh already-installed pipx apps to latest (keeps a re-run current)
run_as_user "pipx upgrade-all" >> "$LOG_FILE" 2>&1 || true
else
warn "pipx unavailable - falling back to pip --user"
ai_pip aider-chat llm files-to-prompt "markitdown[all]" httpie
fi
for probe in "aider:aider" "llm:llm" "files-to-prompt:files-to-prompt" "markitdown:markitdown" "http:httpie"; do
fw_bin="${probe%%:*}"; fw_name="${probe#*:}"
cmd_exists "$fw_bin" && ok "$fw_name installed" && add_result "$fw_name" "INSTALLED" "" \
|| add_result "$fw_name" "SKIPPED" "restart shell"
done
fi
# ── npm-based: repomix + OpenAI Codex CLI ──
if cmd_exists npm; then
step "repomix, Codex, ast-grep, tldr (npm)"
run_as_user "npm install -g repomix @openai/codex @ast-grep/cli tldr" >> "$LOG_FILE" 2>&1 || true
cmd_exists repomix && ok "repomix installed" && add_result "repomix" "INSTALLED" "" \
|| add_result "repomix" "SKIPPED" "restart shell"
cmd_exists ast-grep && ok "ast-grep installed (structural code search/rewrite)" && add_result "ast-grep" "INSTALLED" "" \
|| add_result "ast-grep" "SKIPPED" "restart shell"
cmd_exists tldr && ok "tldr installed" && add_result "tldr" "INSTALLED" "" \
|| add_result "tldr" "SKIPPED" "restart shell"
cmd_exists codex && ok "codex installed (run: codex)" && add_result "OpenAI Codex" "INSTALLED" "" \
|| add_result "OpenAI Codex" "SKIPPED" "restart shell"
info "Codex auth: run 'codex' and sign in with ChatGPT, or set OPENAI_API_KEY."
info "Gemini CLI is omitted by default - Google is sunsetting it ~2026-06-18."
fi
else
info "Skipped AI framework bundle. Re-run anytime to add it."
fi
# ─── Kimi Code CLI (OPT-IN — China-operated; US-only is the default) ─────────
# Hard requirement: Kimi is NEVER installed unattended. It is NOT installed
# under --auto/--all/--everything (those set AUTO_MODE, not WITH_KIMI, and
# ask_yn returns its "n" default under AUTO_MODE), so the unattended
# "everything" path stays US-only by default.
# Interactive users who did NOT pass --with-kimi are still offered it here
# (default No). --with-kimi skips the question; --auto without the flag auto-
# answers No (ask_yn returns the default under AUTO_MODE), so it stays US-only.
if $WITH_KIMI || ask_yn "Install Kimi Code CLI (Moonshot AI — China-operated, off by default)?" "n"; then
section "Installing Kimi Code CLI (opt-in)"
info "Kimi Code is Moonshot AI's standalone 'kimi' terminal coding agent."
warn "Moonshot AI is a China-operated company. Both endpoints"
warn "(api.moonshot.ai global, api.moonshot.cn mainland) are China-operated and"
warn "there is NO published Western data-residency or zero-retention guarantee."
# Reached via --with-kimi, or an interactive "yes" at the gate above.
KIMI_GO=true
if $KIMI_GO; then
if run_as_user "command -v kimi" >/dev/null 2>&1 || cmd_exists kimi; then
ok "Kimi Code already installed ($(run_as_user 'kimi --version' 2>/dev/null | head -1 || echo 'installed'))"
add_result "Kimi Code" "PRESENT" ""
else
step "Installing Kimi Code CLI via official installer (as $ACTUAL_USER)..."
# Per-user CLI — install AS THE REAL INVOKING USER, never root.
run_as_user "curl -LsSf https://code.kimi.com/install.sh | bash" >> "$LOG_FILE" 2>&1 || true
# Kimi installs to the user's home (~/.local/bin); make sure it's on PATH.
export PATH="$ACTUAL_HOME/.local/bin:$PATH"
if run_as_user "command -v kimi" >/dev/null 2>&1 || cmd_exists kimi; then
KIMI_VER="$(run_as_user 'kimi --version' 2>/dev/null | head -1 || echo '')"
ok "Kimi Code installed${KIMI_VER:+ ($KIMI_VER)}"
add_result "Kimi Code" "INSTALLED" "${KIMI_VER:-opt-in}"
else
warn "Kimi Code installed but needs a new shell for PATH"
tip "Close this terminal, open a new one, then run: kimi --version"
add_result "Kimi Code" "INSTALLED" "restart terminal"
fi
fi
# Auth + drop-in recipe + caveat (PRINT only — never auto-export anything).
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ KIMI CODE AUTHENTICATION & USAGE │${NC}"
echo -e " ${CYAN}├──────────────────────────────────────────────────────────────┤${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${WHITE}│ 1. Verify: kimi --version │${NC}"
echo -e " ${WHITE}│ 2. First launch: run 'kimi' -> /login -> \"Kimi Code\" │${NC}"
echo -e " ${WHITE}│ browser OAuth, or paste an API key │${NC}"
echo -e " ${WHITE}│ 3. API key: https://platform.kimi.ai (Console -> create) │${NC}"
echo -e " ${GRAY}│ Account needs >= \$1 recharge to activate. The \$1 │${NC}"
echo -e " ${GRAY}│ starter tier is only 3 RPM; for real agentic loops │${NC}"
echo -e " ${GRAY}│ budget the \$10 tier. │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
echo ""
info "OPTIONAL: use Kimi as a drop-in model INSIDE Claude Code (no separate CLI)"
info "via Moonshot's Anthropic-compatible endpoint. Export these in your shell:"
tip "export ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic"
tip "export ANTHROPIC_AUTH_TOKEN=<your Moonshot key>"
tip "export ANTHROPIC_MODEL=kimi-k2.7-code"
info "Flagship model id: kimi-k2.7-code (released 2026-06-16); also"
info "kimi-k2.6, kimi-k2-thinking. 256K context."
info "(Recipe is PRINTED only — nothing was written to your shell or env.)"
echo ""
warn "CAVEAT: Moonshot AI is China-operated. Neither endpoint"
warn "(api.moonshot.ai global, api.moonshot.cn mainland) publishes a Western"
warn "data-residency or zero-retention guarantee. Want US-only models? Just don't"
warn "pass --with-kimi (that is the default) and Kimi is skipped entirely."
else
info "Skipped Kimi Code at the prompt."
add_result "Kimi Code" "SKIPPED" "declined"
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# AI ECOSYSTEM: MCP SERVERS & ALIGNED REPOS
# ═══════════════════════════════════════════════════════════════════════════════
section "AI Ecosystem: MCP Servers & Aligned Repos"
if cmd_exists claude || run_as_user "command -v claude" >/dev/null 2>&1; then
if ask_yn "Register common MCP servers with Claude Code (user scope)?"; then
run_as_user "mkdir -p '$ACTUAL_HOME/.ai-tools'"
# npm/npx servers (need only Node) — flags BEFORE the name, command after --
run_as_user "claude mcp add --scope user filesystem -- npx -y @modelcontextprotocol/server-filesystem '$ACTUAL_HOME'" >> "$LOG_FILE" 2>&1 || true
run_as_user "claude mcp add --scope user memory -e MEMORY_FILE_PATH='$ACTUAL_HOME/.ai-tools/memory.json' -- npx -y @modelcontextprotocol/server-memory" >> "$LOG_FILE" 2>&1 || true
run_as_user "claude mcp add --scope user sequential-thinking -- npx -y @modelcontextprotocol/server-sequential-thinking" >> "$LOG_FILE" 2>&1 || true
# python/uvx servers (need uv) — fetch + time live on PyPI, not npm
if cmd_exists uvx || run_as_user "command -v uvx" >/dev/null 2>&1; then
run_as_user "claude mcp add --scope user fetch -- uvx mcp-server-fetch" >> "$LOG_FILE" 2>&1 || true
run_as_user "claude mcp add --scope user time -- uvx mcp-server-time" >> "$LOG_FILE" 2>&1 || true
else
info "uvx not found - skipping Python MCP servers (fetch, time). Install uv to add them."
fi
run_as_user "claude mcp add --scope user playwright -- npx -y @playwright/mcp@latest" >> "$LOG_FILE" 2>&1 || true
ok "MCP servers registered (run 'claude mcp list' to verify)"
add_result "MCP servers" "INSTALLED" "fs,memory,seq,fetch,time,pw"
else
add_result "MCP servers" "SKIPPED" ""
fi
else
info "Claude Code not installed - skipping MCP server registration."
fi
echo ""
info "Aligned GitHub repos worth bookmarking:"
info " awesome-claude-code https://github.com/hesreallyhim/awesome-claude-code"
info " awesome-mcp-servers https://github.com/punkpeye/awesome-mcp-servers"
info " MCP reference servers https://github.com/modelcontextprotocol/servers"
info " OpenAI Codex CLI https://github.com/openai/codex"
info " Aider https://github.com/Aider-AI/aider"
if cmd_exists git && ask_yn "Clone these reference repos into ~/ai-tools?" "n"; then
AI_TOOLS_DIR="$ACTUAL_HOME/ai-tools"
run_as_user "mkdir -p '$AI_TOOLS_DIR'"
for repo in \
"https://github.com/hesreallyhim/awesome-claude-code.git" \
"https://github.com/punkpeye/awesome-mcp-servers.git" \
"https://github.com/modelcontextprotocol/servers.git" \
"https://github.com/openai/codex.git" \
"https://github.com/Aider-AI/aider.git"; do
rname="$(basename "$repo" .git)"
if [ -d "$AI_TOOLS_DIR/$rname/.git" ]; then
ok "$rname already cloned"
else
run_as_user "git clone --depth 1 '$repo' '$AI_TOOLS_DIR/$rname'" >> "$LOG_FILE" 2>&1 && ok "cloned $rname" || warn "clone failed: $rname"
fi
done
add_result "Aligned repos" "INSTALLED" "$AI_TOOLS_DIR"
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 3: NETLIFY CLI & DEPLOYMENT TOOLS
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "Deployment & Extras"
section "PHASE 3: Netlify CLI & Deployment"
step "Checking Netlify CLI"
if cmd_exists netlify; then
ok "Netlify CLI already installed ($(netlify --version 2>/dev/null))"
add_result "Netlify CLI" "PRESENT" ""
else
info "Netlify CLI lets you deploy websites directly from your terminal."
info "Free tier: 100GB bandwidth, 300 build minutes/month, custom domains."
if ask_yn "Install Netlify CLI?"; then
if cmd_exists npm; then
run_as_user "npm install -g netlify-cli" >> "$LOG_FILE" 2>&1 || true
export PATH="${NPM_GLOBAL:-$ACTUAL_HOME/.npm-global}/bin:$PATH"
if run_as_user "command -v netlify" &>/dev/null || cmd_exists netlify; then
ok "Netlify CLI installed"
add_result "Netlify CLI" "INSTALLED" ""
else
warn "Netlify CLI installed but needs terminal restart for PATH"
add_result "Netlify CLI" "INSTALLED" "restart terminal"
fi
else
fail "npm not available - install Node.js first"
add_result "Netlify CLI" "FAILED" "npm missing"
fi
else
add_result "Netlify CLI" "SKIPPED" ""
fi
fi
# Netlify authentication guide
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ NETLIFY AUTHENTICATION & DEPLOYMENT │${NC}"
echo -e " ${CYAN}├──────────────────────────────────────────────────────────────┤${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${WHITE}│ LOGIN: │${NC}"
echo -e " ${WHITE}│ netlify login │${NC}"
echo -e " ${WHITE}│ (browser opens -> click Authorize -> return to terminal) │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${WHITE}│ FIRST DEPLOY (link a folder to a Netlify site): │${NC}"
echo -e " ${WHITE}│ cd /path/to/my-website │${NC}"
echo -e " ${WHITE}│ netlify init │${NC}"
echo -e " ${WHITE}│ netlify deploy # preview draft │${NC}"
echo -e " ${WHITE}│ netlify deploy --prod # push live │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${WHITE}│ AI-POWERED DEPLOY (use Claude to edit then deploy): │${NC}"
echo -e " ${WHITE}│ cd /path/to/my-website │${NC}"
echo -e " ${WHITE}│ claude │${NC}"
echo -e " ${WHITE}│ > fix the broken nav menu and deploy a draft to Netlify │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${WHITE}│ ENVIRONMENT VARIABLES: │${NC}"
echo -e " ${WHITE}│ netlify env:set MY_VAR \"value\" │${NC}"
echo -e " ${WHITE}│ netlify env:list │${NC}"
echo -e " ${GRAY}│ │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
if cmd_exists netlify || run_as_user "command -v netlify" &>/dev/null; then
if ask_yn "Log in to Netlify now?" "n"; then
run_as_user "netlify login"
fi
fi
# ─── Cloudflare Wrangler (Pages + Workers) ───────────────────────────────────
step "Checking Cloudflare Wrangler"
if cmd_exists wrangler || run_as_user "command -v wrangler" &>/dev/null; then
ok "Wrangler already installed"
add_result "Cloudflare Wrangler" "PRESENT" ""
else
info "Wrangler deploys to Cloudflare Pages (static sites) and Workers (edge)."
if ask_yn "Install Cloudflare Wrangler?"; then
if cmd_exists npm; then
run_as_user "npm install -g wrangler" >> "$LOG_FILE" 2>&1 || true
if cmd_exists wrangler || run_as_user "command -v wrangler" &>/dev/null; then
ok "Wrangler installed"
add_result "Cloudflare Wrangler" "INSTALLED" ""
else
warn "Wrangler installed but needs terminal restart for PATH"
add_result "Cloudflare Wrangler" "INSTALLED" "restart terminal"
fi
else
fail "npm not available - install Node.js first"
add_result "Cloudflare Wrangler" "FAILED" "npm missing"
fi
else
add_result "Cloudflare Wrangler" "SKIPPED" ""
fi
fi
info "Cloudflare deploy quickstart:"
tip "wrangler login # browser OAuth"
tip "wrangler pages deploy ./dist --project-name=NAME # static site -> Pages"
tip "wrangler deploy # deploy a Worker (edge)"
tip "Headless/CI: export CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID"
if cmd_exists wrangler || run_as_user "command -v wrangler" &>/dev/null; then
if ask_yn "Log in to Cloudflare now?" "n"; then run_as_user "wrangler login"; fi
fi
# ─── Vercel CLI ───────────────────────────────────────────────────────────────
step "Checking Vercel CLI"
if cmd_exists vercel || run_as_user "command -v vercel" &>/dev/null; then
ok "Vercel CLI already installed"
add_result "Vercel CLI" "PRESENT" ""
else
info "Vercel is another popular hosting platform (like Netlify)."
if ask_yn "Install Vercel CLI?" "n"; then
if cmd_exists npm; then
run_as_user "npm install -g vercel" >> "$LOG_FILE" 2>&1 || true
ok "Vercel CLI installed"
add_result "Vercel CLI" "INSTALLED" ""
else
fail "npm not available"
add_result "Vercel CLI" "FAILED" "npm missing"
fi
else
add_result "Vercel CLI" "SKIPPED" ""
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 4: ENVIRONMENT HEALTH CHECK
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "Validation & Health Check"
section "PHASE 4: Environment Health Check"
echo ""
printf " ${WHITE}%-18s %-10s %-35s${NC}\n" "Component" "Status" "Version / Details"
printf " %-18s %-10s %-35s\n" "──────────────────" "──────────" "───────────────────────────────────"
# ─── Functional Validation (not just version checks) ──────────────────────────
step "Running functional validation tests"
# Test Node.js can execute JavaScript
if cmd_exists node; then
NODE_TEST=$(run_as_user "node -e \"console.log('node-ok')\"" 2>/dev/null || true)
if [[ "$NODE_TEST" == *"node-ok"* ]]; then
ok "Node.js exec: PASS"
else
warn "Node.js exec: FAIL - node installed but cannot run JS"
fi
fi
# Test Python SSL and package imports
if [ -n "${PYTHON_CMD:-}" ] && cmd_exists "$PYTHON_CMD"; then
PY_SSL=$(run_as_user "$PYTHON_CMD -c \"import ssl; print('py-ssl-ok:', ssl.OPENSSL_VERSION)\"" 2>/dev/null || true)
if [[ "$PY_SSL" == *"py-ssl-ok"* ]]; then
ok "Python SSL: PASS (${PY_SSL#*: })"
else
warn "Python SSL: FAIL - HTTPS requests may not work"
fi
PY_IMPORT=$(run_as_user "$PYTHON_CMD -c \"import requests, pydantic; print('imports-ok')\"" 2>/dev/null || true)
if [[ "$PY_IMPORT" == *"imports-ok"* ]]; then
ok "Python packages: PASS"
else
warn "Python packages: FAIL - some packages not importable"
fi
fi
# Test npm registry connectivity
if cmd_exists npm; then
NPM_PING=$(run_as_user "npm ping" 2>/dev/null || true)
if [ $? -eq 0 ] || [[ "$NPM_PING" != *"ERR"* ]]; then
ok "npm registry: PASS"
else
warn "npm registry: FAIL - npm cannot reach registry"
fi
fi
# Test Git HTTPS connectivity
if cmd_exists git; then
GIT_LS=$(run_as_user "git ls-remote --heads https://github.com/anthropics/claude-code.git" 2>/dev/null || true)
if [ -n "$GIT_LS" ]; then
ok "Git HTTPS: PASS"
else
warn "Git HTTPS: FAIL - git cannot reach GitHub"
fi
fi
echo ""
CHECKS=(
"Git:git --version"
"Node.js:node --version"
"npm:npm --version"
"npx:npx --version"
"Python:python3 --version"
"pip:python3 -m pip --version"
"uv:uv --version"
"Claude Code:claude --version"
"GitHub CLI:gh --version"
"Netlify CLI:netlify --version"
"Cloudflare Wrangler:wrangler --version"
"Vercel CLI:vercel --version"
"ripgrep:rg --version"
"jq:jq --version"
"ImageMagick:convert --version"
"GhostScript:gs --version"
"FFmpeg:ffmpeg -version"
"tmux:tmux -V"
"sqlite3:sqlite3 --version"
"bat:batcat --version"
"fzf:fzf --version"
"shellcheck:shellcheck --version"
)
PASS_COUNT=0
FAIL_COUNT=0
for check in "${CHECKS[@]}"; do
name="${check%%:*}"
cmd="${check#*:}"
padded_name=$(printf "%-18s" "$name")
ver=$(run_as_user "$cmd" 2>/dev/null | head -1 || true)
if [ -n "$ver" ]; then
truncated="${ver:0:35}"
printf " %s ${GREEN}%-10s${NC} ${GRAY}%s${NC}\n" "$padded_name" "PASS" "$truncated"
PASS_COUNT=$((PASS_COUNT + 1))
else
printf " %s ${RED}%-10s${NC} ${GRAY}%s${NC}\n" "$padded_name" "MISSING" "Not installed"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
done
echo ""
echo -e " ─────────────────────────────────────────────────────────────────"
if [ "$FAIL_COUNT" -eq 0 ]; then
echo -e " ${GREEN}Total: $PASS_COUNT passed, $FAIL_COUNT missing${NC}"
else
echo -e " ${YELLOW}Total: $PASS_COUNT passed, $FAIL_COUNT missing${NC}"
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 5: QUICK REFERENCE GUIDE
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "Complete"
section "PHASE 5: Quick Reference Guide"
echo -e "${CYAN}"
cat << 'GUIDE'
┌─────────────────────────────────────────────────────────────────┐
│ HOW TO USE YOUR AI TERMINALS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CLAUDE CODE (Best for coding, files, agents) │
│ ───────────────────────────────────────────── │
│ claude Start interactive session │
│ claude "fix this bug" One-shot command │
│ claude doctor Diagnose configuration │
│ /help In-session help │
│ │
│ First time: run 'claude' -> browser opens -> log in -> │
│ click Authorize -> return to terminal -> start coding! │
│ │
│ CHATGPT CLI (Best for quick Q&A, brainstorming) │
│ ───────────────────────────────────────────── │
│ chatgpt Start interactive chat (if avail) │
│ Set OPENAI_API_KEY first, then use CLI tools │
│ │
│ GITHUB COPILOT CLI (Best for shell commands) │
│ ───────────────────────────────────────────── │
│ gh copilot suggest "..." Get command suggestions │
│ gh copilot explain "..." Explain a command │
│ gh auth login Log in to GitHub first │
│ │
│ NETLIFY DEPLOYMENT │
│ ────────────────── │
│ netlify login Authenticate │
│ cd /path/to/site │
│ netlify init Link folder to Netlify site │
│ netlify deploy Preview deploy │
│ netlify deploy --prod Production deploy │
│ │
│ AI + DEPLOY WORKFLOW (the magic combo) │
│ ───────────────────────────────────── │
│ cd /path/to/site && claude │
│ > make the hero section more engaging, then deploy │
│ a draft to Netlify so I can preview it │
│ │
│ GUI & WEB INTERFACES (when you prefer a visual tool) │
│ ─────────────────────────────────────────────── │
│ Claude web app: https://claude.ai/code │
│ Claude VS Code: Extensions > search "Claude Code" │
│ ChatGPT web: https://chatgpt.com │
│ ChatGPT desktop: https://openai.com/chatgpt/download │
│ Copilot VS Code: Extensions > "GitHub Copilot" │
│ Netlify GUI: https://app.netlify.com │
│ │
│ APP CONNECTORS (via Claude Code MCP Servers) │
│ ──────────────────────────────────────────── │
│ claude mcp add gdrive -- npx @anthropic/gdrive-mcp │
│ claude mcp add slack -- npx @anthropic/slack-mcp │
│ claude mcp add github -- npx @anthropic/github-mcp │
│ claude mcp add playwright -- npx @playwright/mcp@latest │
│ claude mcp list # see all connected services │
│ │
│ COMMON TIPS │
│ ─────────── │
│ - Always cd into your project folder BEFORE starting claude │
│ - Be specific in prompts for better results │
│ - Open a NEW terminal after install for PATH changes │
│ - Run 'claude doctor' if something seems broken │
│ │
└─────────────────────────────────────────────────────────────────┘
GUIDE
echo -e "${NC}"
# ═══════════════════════════════════════════════════════════════════════════════
# FINAL SUMMARY
# ═══════════════════════════════════════════════════════════════════════════════
ELAPSED=$((SECONDS / 60))m$((SECONDS % 60))s
echo -e " ${GREEN}╔═══════════════════════════════════════════════════════════════════╗${NC}"
echo -e " ${GREEN}║ KICKSTART COMPLETE ║${NC}"
echo -e " ${GREEN}╠═══════════════════════════════════════════════════════════════════╣${NC}"
echo -e " ${GREEN}║ ║${NC}"
printf " ${GREEN}║${NC} ${WHITE}%-22s %-12s %-25s${NC} ${GREEN}║${NC}\n" "Component" "Status" "Detail"
printf " ${GREEN}║${NC} %-22s %-12s %-25s ${GREEN}║${NC}\n" "──────────────────────" "────────────" "─────────────────────────"
for i in "${!RESULT_NAMES[@]}"; do
name="${RESULT_NAMES[$i]}"
status="${RESULT_STATUS[$i]}"
detail="${RESULT_DETAIL[$i]:0:25}"
case "$status" in
INSTALLED|PRESENT|RAN|OK) color="$GREEN" ;;
SKIPPED|PARTIAL) color="$YELLOW" ;;
FAILED|ERROR) color="$RED" ;;
*) color="$GRAY" ;;
esac
printf " ${GREEN}║${NC} ${color}%-22s %-12s${NC} ${GRAY}%-25s${NC} ${GREEN}║${NC}\n" "$name" "$status" "$detail"
done
echo -e " ${GREEN}║ ║${NC}"
echo -e " ${GREEN}║ Time elapsed: $ELAPSED ${GREEN}║${NC}"
echo -e " ${GREEN}║ Log file : $LOG_FILE${GREEN}$(printf '%*s' $((35 - ${#LOG_FILE})) '')║${NC}"
echo -e " ${GREEN}║ ║${NC}"
echo -e " ${GREEN}║ NEXT STEPS: ║${NC}"
echo -e " ${GREEN}║ 1. CLOSE this terminal and open a NEW one (PATH refresh) ║${NC}"
echo -e " ${GREEN}║ 2. Run 'claude' to start Claude Code (browser auth first time) ║${NC}"
echo -e " ${GREEN}║ 3. Run 'gh auth login' for GitHub Copilot ║${NC}"
echo -e " ${GREEN}║ 4. Run 'netlify login' to authenticate Netlify ║${NC}"
echo -e " ${GREEN}║ 5. cd into your project folder, then 'claude' to start! ║${NC}"
echo -e " ${GREEN}║ ║${NC}"
echo -e " ${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}"
echo ""
Full macOS script
Expand start-ai-terminal-kickstart-macos.sh — macOS / Homebrew edition (zsh, Apple Silicon + Intel)
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════════════
# AI Terminal Kickstart - macOS Edition
# Supports: macOS 12 Monterey and newer, Apple Silicon (arm64) + Intel (x86_64)
#
# One script to prep a Mac for AI-powered terminal work:
# 1. Claude Code (Anthropic)
# 2. OpenAI Codex CLI
# 3. ChatGPT CLI (OpenAI)
# 4. GitHub Copilot CLI (GitHub)
# + Netlify CLI for direct site deployment
# + AI frameworks & conversion toolkit (aider, llm, repomix, gemini-cli,
# files-to-prompt, markitdown, pandoc, poppler, tesseract, yt-dlp, jc, glow ...)
#
# Usage:
# chmod +x start-ai-terminal-kickstart-macos.sh
# ./start-ai-terminal-kickstart-macos.sh # interactive
# ./start-ai-terminal-kickstart-macos.sh --auto # unattended (yes to all)
# ./start-ai-terminal-kickstart-macos.sh --with-ollama # also install local-model runner
# ./start-ai-terminal-kickstart-macos.sh --with-office # also install LibreOffice (doc conversions)
# ./start-ai-terminal-kickstart-macos.sh --with-kimi # also install Kimi Code CLI (Moonshot AI, China-operated; OFF by default)
#
# IMPORTANT: Do NOT run with sudo. Homebrew refuses to run as root and will
# abort. The script installs into your user-owned Homebrew prefix and never
# needs elevation (Homebrew itself may prompt once for your password when it
# first creates its directories — that is expected).
#
# Author : AI Terminal ops
# Date : 2026-06-15
# ═══════════════════════════════════════════════════════════════════════════════
set -uo pipefail
# NOTE: -e (errexit) is intentionally omitted. This script uses manual error
# handling with || true and explicit checks. set -e causes false exits on
# arithmetic returning 0 and on optional package installs failing.
#
# NOTE: written to run on the bash that ships with macOS (3.2). No bash-4+
# features (associative arrays, mapfile, ${var^^}) are used anywhere.
# ─── Configuration ────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Private, unpredictable temp dir (created below). Installer output can be
# verbose, so keep it out of a world-writable, predictable /tmp path.
TEMP_DIR=""
LOG_FILE=""
AUTO_MODE=false
WITH_OLLAMA=false
WITH_OFFICE=false
WITH_KIMI=false
ACTUAL_USER="$USER"
ACTUAL_HOME="$HOME"
ARCH="$(uname -m)"
# Parse arguments
for arg in "$@"; do
case "$arg" in
--auto|-a|--all|--everything) AUTO_MODE=true ;;
--with-ollama) WITH_OLLAMA=true ;;
--with-office) WITH_OFFICE=true ;;
--with-kimi) WITH_KIMI=true ;;
--help|-h)
echo "Usage: $0 [--auto|--all] [--with-ollama] [--with-office] [--with-kimi]"
echo " --auto, --all Install the full recommended stack, no prompts"
echo " --with-ollama Also install Ollama + a small local model"
echo " --with-office Also install LibreOffice (for document conversions)"
echo " --with-kimi Also install Kimi Code CLI (Moonshot AI, China-operated; OFF by default)"
exit 0
;;
esac
done
TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/ai-kickstart-macos.XXXXXX" 2>/dev/null)" || TEMP_DIR="/tmp/ai-kickstart-macos-$$"
mkdir -p "$TEMP_DIR" 2>/dev/null || true
chmod 700 "$TEMP_DIR" 2>/dev/null || true
LOG_FILE="$TEMP_DIR/kickstart.log"
( umask 077; : > "$LOG_FILE" )
# Node.js LTS major — the ONLY pinned version in this script (a ~yearly stability
# knob). Every AI tool below installs its latest published release, so the script
# never needs editing when those tools ship new versions.
NODE_MAJOR="22"
# ─── Helper Functions ────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
WHITE='\033[1;37m'
GRAY='\033[0;37m'
NC='\033[0m' # No Color
log() { echo "[$(date +%H:%M:%S)] $*" >> "$LOG_FILE"; }
section() { echo -e "\n ${CYAN}=================================================================${NC}"; echo -e " ${CYAN}$1${NC}"; echo -e " ${CYAN}=================================================================${NC}"; log "SECTION: $1"; }
step() { echo -e "\n ${CYAN}>> $1${NC}"; log "STEP: $1"; }
ok() { echo -e " ${GREEN}[OK]${NC} $1"; log "OK: $1"; }
warn() { echo -e " ${YELLOW}[WARN]${NC} $1"; log "WARN: $1"; }
fail() { echo -e " ${RED}[FAIL]${NC} $1"; log "FAIL: $1"; }
info() { echo -e " ${GRAY}$1${NC}"; }
tip() { echo -e " ${YELLOW}[TIP]${NC} $1"; }
cmd_exists() { command -v "$1" >/dev/null 2>&1; }
# True if ANY of the space-separated candidate commands exists (handles tools
# whose binary name differs across installers, e.g. "fd" vs "fdfind").
cmd_exists_any() {
local c
for c in $1; do
command -v "$c" >/dev/null 2>&1 && return 0
done
return 1
}
# ─── Progress bar ────────────────────────────────────────────────────────────
TOTAL_PHASES=6
CURRENT_PHASE=0
show_progress() {
local label="$1"
CURRENT_PHASE=$((CURRENT_PHASE + 1))
local pct=$((CURRENT_PHASE * 100 / TOTAL_PHASES))
local filled=$((30 * CURRENT_PHASE / TOTAL_PHASES))
local empty=$((30 - filled))
local bar=""; local space=""; local i=0
while [ "$i" -lt "$filled" ]; do bar="${bar}█"; i=$((i + 1)); done
i=0
while [ "$i" -lt "$empty" ]; do space="${space}░"; i=$((i + 1)); done
printf "\r ${CYAN}[%s%s] %3d%% - Phase %d/%d: %s${NC} \n" \
"$bar" "$space" "$pct" "$CURRENT_PHASE" "$TOTAL_PHASES" "$label"
}
# ─── Prompt helper ───────────────────────────────────────────────────────────
ask_yn() {
local prompt="$1"
local default="${2:-y}"
if $AUTO_MODE; then
[ "$default" = "y" ] && return 0 || return 1
fi
local hint="Y/n"
[ "$default" = "n" ] && hint="y/N"
echo -ne " ${YELLOW}$prompt ($hint): ${NC}"
read -r answer
answer="${answer:-$default}"
[[ "$answer" =~ ^[Yy] ]]
}
# Center text inside the banner's inner width (ASCII text only).
BANNER_W=67
center() {
local s="$1"
local len=${#s}
local total=$((BANNER_W - len))
[ "$total" -lt 0 ] && total=0
local left=$((total / 2))
local right=$((total - left))
printf "%*s%s%*s" "$left" "" "$s" "$right" ""
}
# Append a line to the shell profile once (idempotent on a marker).
add_to_profile() {
local line="$1"
local marker="$2"
touch "$PROFILE_FILE" 2>/dev/null || true
if ! grep -qF "$marker" "$PROFILE_FILE" 2>/dev/null; then
printf '\n%s\n' "$line" >> "$PROFILE_FILE"
fi
}
# Write (or REPLACE) an `export VAR="value"` line, anchored to the assignment so
# a rotated key actually takes effect on a re-run (a plain marker check would
# keep the stale key). The profile now holds a secret, so lock it to mode 600.
add_to_profile_kv() {
local var="$1"
local val="$2"
touch "$PROFILE_FILE" 2>/dev/null || true
if grep -q "^export ${var}=" "$PROFILE_FILE" 2>/dev/null; then
local tmp
tmp="$(mktemp)"
grep -v "^export ${var}=" "$PROFILE_FILE" > "$tmp" 2>/dev/null && mv "$tmp" "$PROFILE_FILE"
fi
printf '\nexport %s="%s"\n' "$var" "$val" >> "$PROFILE_FILE"
chmod 600 "$PROFILE_FILE" 2>/dev/null || true
}
# Homebrew install wrapper (formulae). Never elevated.
brew_install() {
HOMEBREW_NO_AUTO_UPDATE=1 brew install "$@" >> "$LOG_FILE" 2>&1 || true
}
brew_install_cask() {
HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask "$@" >> "$LOG_FILE" 2>&1 || true
}
# pip install into the brew Python. Modern Python is "externally managed"
# (PEP 668), so --break-system-packages is required; fall back to --user.
pip_install() {
[ -z "${PYTHON_CMD:-}" ] && return 0
# Homebrew/Apple Python are PEP 668 "externally managed" AND built with
# ENABLE_USER_SITE=False, so --user is rejected here; --break-system-packages
# is the working path. (Standalone CLI apps go through pipx instead.)
"$PYTHON_CMD" -m pip install --break-system-packages --upgrade --quiet "$@" >> "$LOG_FILE" 2>&1 || true
}
# pipx install a CLI app (isolated venv, the correct tool for standalone CLIs).
pipx_install() {
cmd_exists pipx || return 1
pipx install "$@" >> "$LOG_FILE" 2>&1 || pipx install --force "$@" >> "$LOG_FILE" 2>&1 || true
}
# Track results for the final summary (plain indexed arrays — bash 3.2 safe).
RESULT_NAMES=()
RESULT_STATUS=()
RESULT_DETAIL=()
add_result() {
RESULT_NAMES[${#RESULT_NAMES[@]}]="$1"
RESULT_STATUS[${#RESULT_STATUS[@]}]="$2"
RESULT_DETAIL[${#RESULT_DETAIL[@]}]="${3:-}"
}
# ─── Shell profile target (zsh is the macOS default since Catalina) ──────────
case "${SHELL:-/bin/zsh}" in
*/bash) PROFILE_FILE="$ACTUAL_HOME/.bash_profile" ;;
*/zsh) PROFILE_FILE="$ACTUAL_HOME/.zprofile" ;;
*) PROFILE_FILE="$ACTUAL_HOME/.zprofile" ;;
esac
# ─── Banner ───────────────────────────────────────────────────────────────────
echo -e "${MAGENTA}"
printf " ╔"; i=0; while [ "$i" -lt "$BANNER_W" ]; do printf "═"; i=$((i + 1)); done; printf "╗\n"
printf " ║%s║\n" "$(center "")"
printf " ║%s║\n" "$(center "A I T E R M I N A L K I C K S T A R T")"
printf " ║%s║\n" "$(center "macOS Edition (Apple Silicon + Intel)")"
printf " ║%s║\n" "$(center "")"
printf " ║%s║\n" "$(center "Claude Code | Codex | ChatGPT CLI | Copilot CLI")"
printf " ║%s║\n" "$(center "")"
printf " ╚"; i=0; while [ "$i" -lt "$BANNER_W" ]; do printf "═"; i=$((i + 1)); done; printf "╝\n"
echo -e "${NC}"
# ─── Guards: must be macOS, must NOT be root ─────────────────────────────────
if [ "$(uname -s)" != "Darwin" ]; then
fail "This is the macOS edition. You're not on macOS ($(uname -s))."
info "Use start-ai-terminal-kickstart.sh (Linux) or Start-AITerminalKickstart.ps1 (Windows)."
exit 1
fi
if [ "$(id -u)" -eq 0 ]; then
fail "Do NOT run this script with sudo / as root."
info "Homebrew refuses to run as root and the install will fail."
info "Re-run as your normal user: ./start-ai-terminal-kickstart-macos.sh"
exit 1
fi
# ─── System Info ──────────────────────────────────────────────────────────────
MACOS_VER="$(sw_vers -productVersion 2>/dev/null || echo 'unknown')"
MACOS_NAME="$(sw_vers -productName 2>/dev/null || echo 'macOS')"
if [ "$ARCH" = "arm64" ]; then
CHIP_LABEL="Apple Silicon (arm64)"
BREW_PREFIX_DEFAULT="/opt/homebrew"
else
CHIP_LABEL="Intel (x86_64)"
BREW_PREFIX_DEFAULT="/usr/local"
fi
echo -e " ${WHITE}System Info:${NC}"
echo -e " ${GRAY}OS : $MACOS_NAME $MACOS_VER${NC}"
echo -e " ${GRAY}Chip : $CHIP_LABEL${NC}"
echo -e " ${GRAY}User : $ACTUAL_USER${NC}"
echo -e " ${GRAY}Shell : ${SHELL:-unknown}${NC}"
echo -e " ${GRAY}Profile : $PROFILE_FILE${NC}"
echo -e " ${GRAY}Bash : ${BASH_VERSION:-?}${NC}"
echo -e " ${GRAY}Log File : $LOG_FILE${NC}"
echo ""
# ─── Install mode (Everything vs Choose) ─────────────────────────────────────
if ! $AUTO_MODE; then
echo -e " ${WHITE}Install mode:${NC}"
echo -e " ${GRAY} 1) Everything — install the full recommended stack, unattended${NC}"
echo -e " ${GRAY} 2) Choose — ask before each component${NC}"
echo -ne " ${YELLOW}Pick [1/2] (default 2): ${NC}"
read -r INSTALL_MODE
if [ "${INSTALL_MODE:-2}" = "1" ]; then
AUTO_MODE=true
info "Mode: install everything. (API keys, Ollama and LibreOffice stay opt-in via flags.)"
else
info "Mode: choose each component."
fi
echo ""
fi
# ─── Pre-flight: Xcode CLT, Homebrew, Network, Disk ──────────────────────────
show_progress "Pre-flight Checks"
section "PRE-FLIGHT CHECKS"
{
echo ""
echo "======================================================================"
echo "Session started: $(date '+%Y-%m-%d %H:%M:%S')"
echo "OS: $MACOS_NAME $MACOS_VER | Chip: $CHIP_LABEL | User: $ACTUAL_USER"
echo "======================================================================"
} >> "$LOG_FILE"
# --- Xcode Command Line Tools (provides git, clang, make) ---
step "Checking Xcode Command Line Tools"
if xcode-select -p >/dev/null 2>&1; then
ok "Xcode Command Line Tools present ($(xcode-select -p))"
add_result "Xcode CLT" "PRESENT" ""
elif command -v brew >/dev/null 2>&1 || [ -x /opt/homebrew/bin/brew ] || [ -x /usr/local/bin/brew ]; then
# Homebrew is already here but CLT is somehow missing — let Apple fix it.
warn "Xcode Command Line Tools not installed (needed for git + compilers)."
info "Triggering Apple's installer — a GUI dialog will appear. Click Install."
xcode-select --install >/dev/null 2>&1 || true
info "When the install finishes, re-run this script if git is missing."
add_result "Xcode CLT" "TRIGGERED" "complete GUI dialog"
else
# No Homebrew yet: the Homebrew installer (next step) installs the Command
# Line Tools itself. Don't fire a second, racing xcode-select install.
warn "Xcode Command Line Tools not installed."
info "The Homebrew installer below will set them up for you."
add_result "Xcode CLT" "VIA-BREW" "Homebrew installs it"
fi
# --- Homebrew ---
step "Checking Homebrew"
if ! cmd_exists brew; then
# Try known install locations before deciding to install
if [ -x "$BREW_PREFIX_DEFAULT/bin/brew" ]; then
eval "$("$BREW_PREFIX_DEFAULT/bin/brew" shellenv)"
elif [ -x /opt/homebrew/bin/brew ]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [ -x /usr/local/bin/brew ]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
fi
if ! cmd_exists brew; then
info "Homebrew is the macOS package manager — it installs everything else."
if ask_yn "Install Homebrew now?"; then
step "Installing Homebrew (this may prompt once for your password)"
# Run visibly (not redirected) so the sudo password prompt is shown.
NONINTERACTIVE=1 /bin/bash -c \
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || true
# Load brew into this shell from whichever prefix it landed in
if [ -x "$BREW_PREFIX_DEFAULT/bin/brew" ]; then
eval "$("$BREW_PREFIX_DEFAULT/bin/brew" shellenv)"
elif [ -x /opt/homebrew/bin/brew ]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [ -x /usr/local/bin/brew ]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
fi
fi
if cmd_exists brew; then
BREW_BIN="$(command -v brew)"
ok "Homebrew ready ($(brew --version 2>/dev/null | head -1))"
# Persist brew into the login shell so future terminals find it
add_to_profile "eval \"\$($BREW_BIN shellenv)\"" "brew shellenv"
add_result "Homebrew" "OK" "$BREW_BIN"
else
fail "Homebrew is not available. Cannot continue without it."
info "Install manually from https://brew.sh then re-run this script."
add_result "Homebrew" "FAILED" ""
exit 1
fi
# --- Network connectivity ---
step "Testing internet connectivity to download sources"
REACHABLE=0
UNREACHABLE=0
NET_ENDPOINTS="
GitHub API|https://api.github.com
Homebrew|https://formulae.brew.sh
Claude installer|https://claude.ai
npm registry|https://registry.npmjs.org
PyPI|https://pypi.org
"
while IFS='|' read -r ep_name ep_url; do
[ -z "$ep_name" ] && continue
if curl -sf --connect-timeout 8 --max-time 12 -o /dev/null "$ep_url" 2>/dev/null; then
ok "$ep_name reachable"
REACHABLE=$((REACHABLE + 1))
else
warn "$ep_name ($ep_url) - UNREACHABLE"
UNREACHABLE=$((UNREACHABLE + 1))
fi
done <<< "$NET_ENDPOINTS"
TOTAL_EP=$((REACHABLE + UNREACHABLE))
if [ "$UNREACHABLE" -eq 0 ]; then
ok "All $REACHABLE download sources reachable"
add_result "Network" "OK" "$REACHABLE/$TOTAL_EP reachable"
elif [ "$UNREACHABLE" -lt 3 ]; then
warn "$UNREACHABLE of $TOTAL_EP sources unreachable. Some installs may fail."
add_result "Network" "PARTIAL" "$REACHABLE/$TOTAL_EP reachable"
else
fail "Most download sources unreachable. Script will likely fail."
info "Check: DNS, proxy, firewall, or VPN settings."
add_result "Network" "FAILED" "$REACHABLE/$TOTAL_EP reachable"
fi
# --- Disk space (BSD df: -g reports GB) ---
step "Checking available disk space"
FREE_GB="$(df -g / 2>/dev/null | awk 'NR==2{print $4}')"
TOTAL_GB="$(df -g / 2>/dev/null | awk 'NR==2{print $2}')"
if [ "${FREE_GB:-0}" -lt 3 ]; then
fail "CRITICAL: only ${FREE_GB}GB free on / (${TOTAL_GB}GB total). Need at least 3GB."
add_result "Disk Space" "FAILED" "${FREE_GB}GB free"
elif [ "${FREE_GB:-0}" -lt 8 ]; then
warn "Low: ${FREE_GB}GB free on / (${TOTAL_GB}GB total). Recommend 8+ GB."
add_result "Disk Space" "WARN" "${FREE_GB}GB free"
else
ok "Disk space: ${FREE_GB}GB free on / (${TOTAL_GB}GB total)"
add_result "Disk Space" "OK" "${FREE_GB}GB free"
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 1: CORE RUNTIMES & CLI UTILITIES
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "Core Runtimes & Utilities"
section "PHASE 1: Core Runtimes & CLI Utilities"
step "Updating Homebrew"
brew update >> "$LOG_FILE" 2>&1 || true
ok "Homebrew updated"
# ─── Git ──────────────────────────────────────────────────────────────────────
step "Checking Git"
if cmd_exists git; then
ok "Git already installed ($(git --version))"
add_result "Git" "PRESENT" "$(git --version)"
else
info "Git is REQUIRED for Claude Code and version control."
if ask_yn "Install Git (Homebrew, newer than the Xcode one)?"; then
brew_install git
cmd_exists git && ok "Git installed ($(git --version))" && add_result "Git" "INSTALLED" "$(git --version)"
else
add_result "Git" "SKIPPED" ""
fi
fi
# Git identity check
if cmd_exists git; then
step "Checking Git configuration"
GIT_USER="$(git config --global user.name 2>/dev/null || true)"
GIT_EMAIL="$(git config --global user.email 2>/dev/null || true)"
if [ -z "$GIT_USER" ] || [ -z "$GIT_EMAIL" ]; then
warn "Git user.name or user.email not set. Commits will fail without these."
tip "git config --global user.name 'Your Name'"
tip "git config --global user.email 'you@example.com'"
else
ok "Git user: $GIT_USER <$GIT_EMAIL>"
fi
git config --global init.defaultBranch main 2>/dev/null || true
fi
# ─── Node.js ──────────────────────────────────────────────────────────────────
step "Checking Node.js"
if cmd_exists node; then
ok "Node.js already installed ($(node --version))"
cmd_exists npm && ok "npm $(npm --version)"
cmd_exists npx && ok "npx available"
add_result "Node.js" "PRESENT" "$(node --version)"
else
info "Node.js is required for Claude Code, npm tools, and MCP servers."
if ask_yn "Install Node.js ${NODE_MAJOR} LTS?"; then
brew_install "node@${NODE_MAJOR}"
# node@NN is keg-only; link it onto PATH
brew link --overwrite --force "node@${NODE_MAJOR}" >> "$LOG_FILE" 2>&1 || true
if ! cmd_exists node; then brew_install node; fi
if cmd_exists node; then
ok "Node.js $(node --version) installed"
cmd_exists npm && ok "npm $(npm --version)"
add_result "Node.js" "INSTALLED" "$(node --version)"
else
fail "Node.js installation failed"
add_result "Node.js" "FAILED" ""
fi
else
add_result "Node.js" "SKIPPED" ""
fi
fi
# npm global prefix (user-owned; brew node already installs globals without sudo,
# but a dedicated prefix keeps things tidy and survives node upgrades).
if cmd_exists npm; then
step "Configuring npm global directory"
NPM_GLOBAL="$ACTUAL_HOME/.npm-global"
mkdir -p "$NPM_GLOBAL"
npm config set prefix "$NPM_GLOBAL" >> "$LOG_FILE" 2>&1 || true
add_to_profile 'export PATH="$HOME/.npm-global/bin:$PATH"' 'npm-global'
export PATH="$NPM_GLOBAL/bin:$PATH"
ok "npm global dir: $NPM_GLOBAL"
fi
# ─── Python (prefer Homebrew Python, not Apple's externally-managed one) ─────
step "Checking Python"
# Apple's /usr/bin/python3 is externally-managed and its user-site bin is not on
# PATH, so we deliberately ignore it and prefer a Homebrew python3.x.
PYTHON_CMD=""
pick_brew_python() {
local cand p
for cand in python3.13 python3.12 python3.11 python3; do
if command -v "$cand" >/dev/null 2>&1; then
p="$(command -v "$cand")"
case "$p" in /usr/bin/python3|/usr/bin/python3.*) continue ;; esac
PYTHON_CMD="$cand"; return 0
fi
done
return 1
}
pick_brew_python || true
if [ -n "$PYTHON_CMD" ]; then
ok "Python: $("$PYTHON_CMD" --version 2>&1) ($PYTHON_CMD)"
add_result "Python" "PRESENT" "$("$PYTHON_CMD" --version 2>&1)"
else
info "Python is used for MCP servers, AI SDKs, and data processing."
info "(Apple's system python3 is externally-managed; installing Homebrew Python.)"
if ask_yn "Install Python 3 (Homebrew)?"; then
brew_install python # unversioned formula = latest stable Python; no version pin
pick_brew_python || PYTHON_CMD="python3"
if "$PYTHON_CMD" --version >/dev/null 2>&1; then
ok "Python $("$PYTHON_CMD" --version 2>&1) installed ($PYTHON_CMD)"
add_result "Python" "INSTALLED" "$("$PYTHON_CMD" --version 2>&1)"
else
fail "Python installation failed"
add_result "Python" "FAILED" ""
fi
else
add_result "Python" "SKIPPED" ""
fi
fi
# pipx — the right tool for installing standalone Python CLI apps in isolation.
if [ -n "$PYTHON_CMD" ]; then
step "Checking pipx (isolated installs for Python CLI apps)"
if cmd_exists pipx; then
ok "pipx already installed"
add_result "pipx" "PRESENT" ""
else
brew_install pipx
pipx ensurepath >> "$LOG_FILE" 2>&1 || true
add_to_profile 'export PATH="$HOME/.local/bin:$PATH"' '.local/bin'
export PATH="$ACTUAL_HOME/.local/bin:$PATH"
cmd_exists pipx && ok "pipx installed" && add_result "pipx" "INSTALLED" ""
fi
fi
# uv — ultra-fast Python package/venv manager.
step "Checking uv (fast Python package manager)"
if cmd_exists uv; then
ok "uv already installed ($(uv --version 2>/dev/null))"
add_result "uv" "PRESENT" ""
else
if ask_yn "Install uv?"; then
brew_install uv
cmd_exists uv && ok "uv installed" && add_result "uv" "INSTALLED" "" || add_result "uv" "SKIPPED" ""
else
add_result "uv" "SKIPPED" ""
fi
fi
# ─── CLI Utilities + Conversion Toolkit (single bash-3.2-safe table) ─────────
step "Installing CLI utilities + conversion toolkit"
# key | candidate-commands | brew-formula | description
CLI_TABLE="
ripgrep|rg|ripgrep|Ultra-fast code search (used by Claude Code)
jq|jq|jq|JSON processor for API responses
fd|fd fdfind|fd|Fast file finder
imagemagick|magick convert|imagemagick|Image processing (convert, resize)
ghostscript|gs|ghostscript|PDF/PostScript engine (needed by ImageMagick for PDFs)
ffmpeg|ffmpeg|ffmpeg|Video/audio processing and media conversion
p7zip|7z 7zz 7za|p7zip|Archive compression
bat|bat batcat|bat|Syntax-highlighted file viewer (better cat)
fzf|fzf|fzf|Fuzzy finder for files, history, and commands
yq|yq|yq|YAML processor (like jq but for YAML)
tree|tree|tree|Directory structure viewer
delta|delta|git-delta|Enhanced git diff viewer with syntax highlighting
shellcheck|shellcheck|shellcheck|Shell script linter and validator
tmux|tmux|tmux|Terminal multiplexer for persistent sessions
sqlite3|sqlite3|sqlite|Lightweight database engine and client
wget|wget|wget|Classic downloader (curl alternative)
gnupg|gpg|gnupg|GnuPG for signing and encryption
pandoc|pandoc|pandoc|Universal document converter (docx/md/pdf/html ...)
poppler|pdftotext pdfinfo|poppler|PDF text + image extraction (feed PDFs to AI)
tesseract|tesseract|tesseract|OCR engine (turn images/scans into text)
yt-dlp|yt-dlp|yt-dlp|Media downloader (pairs with ffmpeg)
jc|jc|jc|Convert command output to JSON for AI parsing
glow|glow|glow|Render Markdown beautifully in the terminal
gh|gh|gh|GitHub CLI (also powers Copilot CLI)
gron|gron|gron|Greppable JSON (gron / gron -u) for AI + jq workflows
just|just|just|Command/task runner (justfile recipes)
"
# macOS GNU userland + quality-of-life (binaries are g-prefixed; check formula)
MAC_TABLE="
coreutils|gdate gtimeout|coreutils|GNU core utilities (gdate, gtimeout, gln ...)
gnu-sed|gsed|gnu-sed|GNU sed (gsed) — matches Linux sed behavior
findutils|gfind|findutils|GNU find/xargs (gfind, gxargs)
gawk|gawk|gawk|GNU awk
grep|ggrep|grep|GNU grep (ggrep)
watch|watch|watch|Re-run a command on an interval
trash|trash|trash|Move files to Trash instead of rm
terminal-notifier|terminal-notifier|terminal-notifier|macOS notifications from the CLI
mas|mas|mas|Mac App Store command-line installer
"
install_table() {
local table="$1"
local key cmds formula desc pretty
while IFS='|' read -r key cmds formula desc; do
[ -z "$key" ] && continue
if cmd_exists_any "$cmds"; then
ok "$key is installed"
add_result "$key" "PRESENT" ""
else
info "$key: $desc"
if ask_yn "Install $key?"; then
brew_install "$formula"
if cmd_exists_any "$cmds"; then
ok "$key installed"
add_result "$key" "INSTALLED" ""
else
warn "$key may need a new shell to appear on PATH"
add_result "$key" "INSTALLED" "restart shell"
fi
else
add_result "$key" "SKIPPED" ""
fi
fi
done <<< "$table"
}
install_table "$CLI_TABLE"
step "Installing macOS power tools (GNU userland + quality-of-life)"
install_table "$MAC_TABLE"
# ─── Python packages for AI development ──────────────────────────────────────
if [ -n "$PYTHON_CMD" ]; then
step "Python packages for AI development"
info "These libraries enhance what Claude Code and AI tools can do:"
info " Data: numpy, pandas, polars, matplotlib, openpyxl"
info " Web : requests, httpx, beautifulsoup4"
info " AI : anthropic, openai, fastmcp, mcp"
info " Dev : pydantic, rich, pyyaml, python-dotenv"
if ask_yn "Install recommended Python packages?"; then
pip_install numpy pandas polars matplotlib openpyxl requests httpx \
beautifulsoup4 lxml anthropic openai fastmcp mcp pydantic rich \
pyyaml python-dotenv Pillow chardet tabulate jsonlines
ok "Python packages installed (into Homebrew Python via --break-system-packages)"
add_result "Python Packages" "INSTALLED" "AI + data libs"
else
add_result "Python Packages" "SKIPPED" ""
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 2: AI TERMINALS, FRAMEWORKS & CONVERSIONS
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "AI Terminals & Frameworks"
section "PHASE 2: AI Terminal Solutions"
echo ""
echo -e " ${WHITE}Which AI terminal solutions would you like to set up?${NC}"
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ 1. Claude Code (Anthropic) │${NC}"
echo -e " ${GRAY}│ Best for: deep code understanding, multi-file edits, │${NC}"
echo -e " ${GRAY}│ agentic coding, MCP servers, security research │${NC}"
echo -e " ${CYAN}│ 2. OpenAI Codex CLI │${NC}"
echo -e " ${GRAY}│ Best for: OpenAI-model agentic coding in the terminal │${NC}"
echo -e " ${CYAN}│ 3. ChatGPT CLI (OpenAI) │${NC}"
echo -e " ${GRAY}│ Best for: quick questions, brainstorming, general AI │${NC}"
echo -e " ${CYAN}│ 4. GitHub Copilot CLI (GitHub) │${NC}"
echo -e " ${GRAY}│ Best for: shell command suggestions, git operations │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
echo ""
INSTALL_CLAUDE=false
INSTALL_CODEX=false
INSTALL_CHATGPT=false
INSTALL_COPILOT=false
ask_yn "Install Claude Code?" && INSTALL_CLAUDE=true
ask_yn "Install OpenAI Codex CLI?" && INSTALL_CODEX=true
ask_yn "Install ChatGPT CLI tools?" && INSTALL_CHATGPT=true
ask_yn "Install GitHub Copilot CLI?" && INSTALL_COPILOT=true
# ─── Claude Code ──────────────────────────────────────────────────────────────
if $INSTALL_CLAUDE; then
section "Installing Claude Code"
if cmd_exists claude; then
ok "Claude Code already installed ($(claude --version 2>/dev/null || echo installed))"
add_result "Claude Code" "PRESENT" ""
else
step "Installing Claude Code via official installer..."
curl -fsSL https://claude.ai/install.sh | sh >> "$LOG_FILE" 2>&1 || true
add_to_profile 'export PATH="$HOME/.local/bin:$PATH"' '.local/bin'
export PATH="$ACTUAL_HOME/.local/bin:$PATH"
if cmd_exists claude; then
ok "Claude Code installed!"
add_result "Claude Code" "INSTALLED" ""
elif cmd_exists npm; then
warn "Official installer may need a new shell. Trying npm fallback..."
npm install -g @anthropic-ai/claude-code >> "$LOG_FILE" 2>&1 || true
if cmd_exists claude; then
ok "Claude Code installed via npm"
add_result "Claude Code" "INSTALLED" "via npm"
else
fail "Claude Code install needs a terminal restart"
tip "Open a new terminal, then run: claude"
add_result "Claude Code" "INSTALLED" "restart terminal"
fi
else
fail "npm not available for fallback install"
add_result "Claude Code" "FAILED" "npm missing"
fi
fi
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ CLAUDE CODE AUTHENTICATION │${NC}"
echo -e " ${CYAN}├──────────────────────────────────────────────────────────────┤${NC}"
echo -e " ${GRAY}│ Option A - Browser login (easiest): │${NC}"
echo -e " ${WHITE}│ 1. Run: claude │${NC}"
echo -e " ${WHITE}│ 2. Browser opens -> log in at claude.ai -> Authorize │${NC}"
echo -e " ${GRAY}│ Option B - API key: │${NC}"
echo -e " ${WHITE}│ export ANTHROPIC_API_KEY=\"sk-ant-your-key\" │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
if ask_yn "Set ANTHROPIC_API_KEY now?" "n"; then
echo -ne " ${YELLOW}Enter your Anthropic API key (sk-ant-...): ${NC}"
read -r ANTHROPIC_KEY
if [[ "$ANTHROPIC_KEY" == sk-ant-* ]]; then
add_to_profile_kv ANTHROPIC_API_KEY "$ANTHROPIC_KEY"
export ANTHROPIC_API_KEY="$ANTHROPIC_KEY"
ok "ANTHROPIC_API_KEY saved to $PROFILE_FILE (mode 600)"
else
warn "Key doesn't start with sk-ant- . Skipped. Use browser login instead."
fi
fi
fi
# ─── OpenAI Codex CLI ─────────────────────────────────────────────────────────
if $INSTALL_CODEX; then
section "Installing OpenAI Codex CLI"
if cmd_exists codex; then
ok "Codex already installed ($(codex --version 2>/dev/null || echo installed))"
add_result "OpenAI Codex" "PRESENT" ""
else
step "Installing Codex..."
if cmd_exists npm; then
npm install -g @openai/codex >> "$LOG_FILE" 2>&1 || true
fi
if ! cmd_exists codex; then
brew_install_cask codex # Codex ships as a Homebrew cask, not a formula
fi
if cmd_exists codex; then
ok "Codex installed (run: codex)"
add_result "OpenAI Codex" "INSTALLED" ""
else
warn "Codex install needs a terminal restart"
add_result "OpenAI Codex" "INSTALLED" "restart terminal"
fi
fi
info "Auth: run 'codex' and sign in with ChatGPT, or set OPENAI_API_KEY."
fi
# ─── ChatGPT CLI ──────────────────────────────────────────────────────────────
if $INSTALL_CHATGPT; then
section "Installing ChatGPT CLI Tools"
if cmd_exists npm; then
npm install -g chatgpt-cli >> "$LOG_FILE" 2>&1 || true
ok "chatgpt-cli installed (run: chatgpt)"
add_result "ChatGPT CLI" "INSTALLED" "chatgpt-cli"
else
warn "npm not available - cannot install ChatGPT CLI"
add_result "ChatGPT CLI" "FAILED" "npm missing"
fi
[ -n "${PYTHON_CMD:-}" ] && pip_install openai && ok "OpenAI Python SDK installed"
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ CHATGPT / OPENAI AUTHENTICATION │${NC}"
echo -e " ${WHITE}│ 1. Get a key: https://platform.openai.com/api-keys │${NC}"
echo -e " ${WHITE}│ 2. export OPENAI_API_KEY=\"sk-your-key\" │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
if ask_yn "Set OPENAI_API_KEY now?" "n"; then
echo -ne " ${YELLOW}Enter your OpenAI API key (sk-...): ${NC}"
read -r OPENAI_KEY
if [[ "$OPENAI_KEY" == sk-* ]]; then
add_to_profile_kv OPENAI_API_KEY "$OPENAI_KEY"
export OPENAI_API_KEY="$OPENAI_KEY"
ok "OPENAI_API_KEY saved to $PROFILE_FILE (mode 600)"
else
warn "Key format not recognized. Set it manually later."
fi
fi
fi
# ─── GitHub Copilot CLI ───────────────────────────────────────────────────────
if $INSTALL_COPILOT; then
section "Installing GitHub Copilot CLI"
if ! cmd_exists gh; then
info "GitHub CLI is required for Copilot CLI."
if ask_yn "Install GitHub CLI?"; then brew_install gh; fi
fi
if cmd_exists gh; then
ok "GitHub CLI: $(gh --version 2>/dev/null | head -1)"
add_result "GitHub CLI" "PRESENT" ""
step "Installing Copilot CLI extension..."
gh extension install github/gh-copilot >> "$LOG_FILE" 2>&1 || true
ok "Copilot CLI extension installed"
add_result "GitHub Copilot CLI" "INSTALLED" ""
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ GITHUB COPILOT AUTHENTICATION │${NC}"
echo -e " ${WHITE}│ 1. gh auth login -> GitHub.com -> web browser │${NC}"
echo -e " ${WHITE}│ 2. Copy the one-time code, authorize in the browser │${NC}"
echo -e " ${GRAY}│ Requires a GitHub Copilot subscription. │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
if ask_yn "Log in to GitHub now?" "n"; then gh auth login; fi
else
fail "GitHub CLI not available"
add_result "GitHub Copilot CLI" "FAILED" "gh CLI missing"
fi
fi
# ─── AI frameworks & conversion CLIs (code generation + data conversions) ────
section "AI Frameworks & Conversion CLIs"
info "These power code generation, repo-to-prompt packing, and feeding"
info "documents/media into any AI model."
echo ""
if ask_yn "Install the AI framework + conversion bundle?"; then
# npm-based agentic / repo tooling
if cmd_exists npm; then
step "repomix, ast-grep, tldr (npm)"
npm install -g repomix @ast-grep/cli tldr >> "$LOG_FILE" 2>&1 || true
cmd_exists repomix && ok "repomix installed" && add_result "repomix" "INSTALLED" "" || add_result "repomix" "SKIPPED" "restart shell"
cmd_exists ast-grep && ok "ast-grep installed (structural code search/rewrite)" && add_result "ast-grep" "INSTALLED" "" || add_result "ast-grep" "SKIPPED" "restart shell"
cmd_exists tldr && ok "tldr installed" && add_result "tldr" "INSTALLED" "" || add_result "tldr" "SKIPPED" "restart shell"
# Google Gemini CLI installs the '@google/gemini-cli' package (command:
# 'gemini'). NOTE: Google is sunsetting Gemini CLI ~2026-06-18, so it is
# opt-in here and skipped under --auto.
if $AUTO_MODE; then
info "Skipping Google Gemini CLI under --auto (Google is sunsetting it ~2026-06-18)."
elif ask_yn "Install Google Gemini CLI? (Google is sunsetting it ~2026-06-18)" "n"; then
npm install -g @google/gemini-cli >> "$LOG_FILE" 2>&1 || true
cmd_exists gemini && ok "gemini installed (command: gemini)" && add_result "gemini-cli" "INSTALLED" "" || add_result "gemini-cli" "SKIPPED" "restart shell"
fi
fi
# pipx-based standalone Python AI CLIs (isolated, no PEP 668 conflicts)
if cmd_exists pipx; then
step "aider (AI pair programmer in the terminal)"
pipx_install aider-chat
cmd_exists aider && ok "aider installed" && add_result "aider" "INSTALLED" "" || add_result "aider" "SKIPPED" "restart shell"
step "llm (Simon Willison's pluggable LLM CLI)"
pipx_install llm
cmd_exists llm && ok "llm installed" && add_result "llm" "INSTALLED" "" || add_result "llm" "SKIPPED" "restart shell"
step "files-to-prompt (turn a tree of files into one prompt)"
pipx_install files-to-prompt
cmd_exists files-to-prompt && ok "files-to-prompt installed" && add_result "files-to-prompt" "INSTALLED" "" || add_result "files-to-prompt" "SKIPPED" "restart shell"
step "markitdown (Office/PDF/images -> Markdown for LLMs)"
pipx_install "markitdown[all]"
cmd_exists markitdown && ok "markitdown installed" && add_result "markitdown" "INSTALLED" "" || add_result "markitdown" "SKIPPED" "restart shell"
step "httpie (friendly HTTP client; the command is 'http')"
pipx_install httpie
cmd_exists http && ok "httpie installed (command: http)" && add_result "httpie" "INSTALLED" "" || add_result "httpie" "SKIPPED" "restart shell"
# Refresh any already-installed pipx apps to their latest release (so a
# re-run keeps the toolchain current without editing the script).
pipx upgrade-all >> "$LOG_FILE" 2>&1 || true
else
warn "pipx unavailable - skipping aider/llm/files-to-prompt/markitdown"
fi
# aichat — all-in-one Rust LLM CLI (brew formula)
step "aichat (all-in-one LLM CLI: chat, REPL, RAG, shell assistant)"
if cmd_exists aichat; then
ok "aichat already installed"
add_result "aichat" "PRESENT" ""
else
brew_install aichat
cmd_exists aichat && ok "aichat installed" && add_result "aichat" "INSTALLED" "" || add_result "aichat" "SKIPPED" ""
fi
else
info "Skipped AI framework bundle. Re-run anytime to add them."
fi
# ─── Optional: Ollama (local models) ─────────────────────────────────────────
if $WITH_OLLAMA || ask_yn "Install Ollama for offline/local models?" "n"; then
section "Installing Ollama (local model runner)"
if cmd_exists ollama; then
ok "Ollama already installed"
add_result "Ollama" "PRESENT" ""
else
brew_install ollama
cmd_exists ollama && ok "Ollama installed" && add_result "Ollama" "INSTALLED" "" || add_result "Ollama" "SKIPPED" ""
fi
if cmd_exists ollama; then
info "Start the server with: ollama serve (or it runs as a brew service)"
if ask_yn "Pull a small starter model now (llama3.2, ~2GB)?" "n"; then
brew services start ollama >> "$LOG_FILE" 2>&1 || (ollama serve >> "$LOG_FILE" 2>&1 &) || true
sleep 2
ollama pull llama3.2 || warn "Pull failed - run 'ollama pull llama3.2' later"
fi
fi
fi
# ─── Optional: Kimi Code CLI (Moonshot AI — China-operated, OFF by default) ──
# Gated EXACTLY like Ollama: opt-in via --with-kimi, otherwise prompted with a
# default of "n". Under --auto/--all/--everything, ask_yn returns the "n"
# default, so the full-stack run does NOT install Kimi. US-only models stay the
# default; a user must explicitly choose this China-operated tool.
if $WITH_KIMI || ask_yn "Install Kimi Code CLI (Moonshot AI — China-operated, off by default)?" "n"; then
section "Installing Kimi Code CLI (Moonshot AI)"
if cmd_exists kimi; then
ok "Kimi Code CLI already installed ($(kimi --version 2>/dev/null || echo installed))"
add_result "Kimi Code CLI" "PRESENT" ""
else
step "Installing Kimi Code CLI via official installer (run as your user, no sudo)..."
curl -LsSf https://code.kimi.com/install.sh | bash >> "$LOG_FILE" 2>&1 || true
add_to_profile 'export PATH="$HOME/.local/bin:$PATH"' '.local/bin'
export PATH="$ACTUAL_HOME/.local/bin:$PATH"
if cmd_exists kimi; then
ok "Kimi Code CLI installed ($(kimi --version 2>/dev/null || echo installed))"
add_result "Kimi Code CLI" "INSTALLED" ""
else
warn "Kimi Code CLI install may need a new shell to appear on PATH"
add_result "Kimi Code CLI" "INSTALLED" "restart shell"
fi
fi
echo ""
info "Kimi Code CLI auth: run 'kimi' then '/login' (browser OAuth: \"Kimi Code\"),"
info "or paste an API key from https://platform.kimi.ai (Console -> create key)."
info "Account needs a >= \$1 recharge to activate; the \$1 starter tier is only"
info "3 RPM, so for real agentic loops budget the \$10 tier."
echo ""
info "OPTIONAL — use Kimi as a drop-in model INSIDE Claude Code (no separate CLI)"
info "via Moonshot's Anthropic-compatible endpoint. Set these in your shell ONLY"
info "when you want Claude Code to talk to Kimi (do NOT set them to keep US models):"
info " export ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic"
info " export ANTHROPIC_AUTH_TOKEN=<your Moonshot key>"
info " export ANTHROPIC_MODEL=kimi-k2.7-code"
info "Leaving those three unset keeps Claude Code on US (Anthropic) models."
warn "Moonshot AI is China-operated (both api.moonshot.ai and api.moonshot.cn) — do NOT send confidential client data through it."
fi
# ─── Optional: LibreOffice (document conversions) ────────────────────────────
if $WITH_OFFICE; then
section "Installing LibreOffice (document conversions)"
if [ -d "/Applications/LibreOffice.app" ] || cmd_exists soffice; then
ok "LibreOffice already installed"
add_result "LibreOffice" "PRESENT" ""
else
brew_install_cask libreoffice
add_result "LibreOffice" "INSTALLED" ""
info "Convert via: soffice --headless --convert-to pdf file.docx"
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# AI ECOSYSTEM: MCP SERVERS & ALIGNED REPOS
# ═══════════════════════════════════════════════════════════════════════════════
section "AI Ecosystem: MCP Servers & Aligned Repos"
if cmd_exists claude; then
if ask_yn "Register common MCP servers with Claude Code (user scope)?"; then
mkdir -p "$ACTUAL_HOME/.ai-tools"
# npm/npx servers (need only Node) — flags BEFORE the name, command after `--`
claude mcp add --scope user filesystem -- npx -y @modelcontextprotocol/server-filesystem "$ACTUAL_HOME" >> "$LOG_FILE" 2>&1 || true
claude mcp add --scope user memory -e MEMORY_FILE_PATH="$ACTUAL_HOME/.ai-tools/memory.json" -- npx -y @modelcontextprotocol/server-memory >> "$LOG_FILE" 2>&1 || true
claude mcp add --scope user sequential-thinking -- npx -y @modelcontextprotocol/server-sequential-thinking >> "$LOG_FILE" 2>&1 || true
# python/uvx servers (need uv) — fetch + time live on PyPI, not npm
if cmd_exists uvx; then
claude mcp add --scope user fetch -- uvx mcp-server-fetch >> "$LOG_FILE" 2>&1 || true
claude mcp add --scope user time -- uvx mcp-server-time >> "$LOG_FILE" 2>&1 || true
else
info "uvx not found — skipping Python MCP servers (fetch, time). 'brew install uv' to add them."
fi
claude mcp add --scope user playwright -- npx -y @playwright/mcp@latest >> "$LOG_FILE" 2>&1 || true
ok "MCP servers registered (run 'claude mcp list' to verify)"
add_result "MCP servers" "INSTALLED" "fs,memory,seq,fetch,time,pw"
else
add_result "MCP servers" "SKIPPED" ""
fi
else
info "Claude Code not installed — skipping MCP server registration."
fi
echo ""
info "Aligned GitHub repos worth bookmarking:"
info " awesome-claude-code https://github.com/hesreallyhim/awesome-claude-code"
info " awesome-mcp-servers https://github.com/punkpeye/awesome-mcp-servers"
info " MCP reference servers https://github.com/modelcontextprotocol/servers"
info " OpenAI Codex CLI https://github.com/openai/codex"
info " Aider https://github.com/Aider-AI/aider"
if cmd_exists git && ask_yn "Clone these reference repos into ~/ai-tools?" "n"; then
AI_TOOLS_DIR="$ACTUAL_HOME/ai-tools"
mkdir -p "$AI_TOOLS_DIR"
for repo in \
"https://github.com/hesreallyhim/awesome-claude-code.git" \
"https://github.com/punkpeye/awesome-mcp-servers.git" \
"https://github.com/modelcontextprotocol/servers.git" \
"https://github.com/openai/codex.git" \
"https://github.com/Aider-AI/aider.git"; do
rname="$(basename "$repo" .git)"
if [ -d "$AI_TOOLS_DIR/$rname/.git" ]; then
ok "$rname already cloned"
else
git clone --depth 1 "$repo" "$AI_TOOLS_DIR/$rname" >> "$LOG_FILE" 2>&1 && ok "cloned $rname" || warn "clone failed: $rname"
fi
done
add_result "Aligned repos" "INSTALLED" "$AI_TOOLS_DIR"
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 3: DEPLOYMENT TOOLS
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "Deployment & Extras"
section "PHASE 3: Netlify CLI & Deployment"
step "Checking Netlify CLI"
if cmd_exists netlify; then
ok "Netlify CLI already installed ($(netlify --version 2>/dev/null))"
add_result "Netlify CLI" "PRESENT" ""
else
info "Netlify CLI deploys websites directly from your terminal."
if ask_yn "Install Netlify CLI?"; then
if cmd_exists npm; then
npm install -g netlify-cli >> "$LOG_FILE" 2>&1 || true
cmd_exists netlify && ok "Netlify CLI installed" && add_result "Netlify CLI" "INSTALLED" "" \
|| { warn "Netlify CLI installed but needs terminal restart for PATH"; add_result "Netlify CLI" "INSTALLED" "restart terminal"; }
else
fail "npm not available - install Node.js first"
add_result "Netlify CLI" "FAILED" "npm missing"
fi
else
add_result "Netlify CLI" "SKIPPED" ""
fi
fi
echo ""
echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}"
echo -e " ${CYAN}│ NETLIFY: LOGIN & DEPLOY │${NC}"
echo -e " ${WHITE}│ netlify login │${NC}"
echo -e " ${WHITE}│ cd /path/to/site && netlify init │${NC}"
echo -e " ${WHITE}│ netlify deploy # preview draft │${NC}"
echo -e " ${WHITE}│ netlify deploy --prod # push live │${NC}"
echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}"
if cmd_exists netlify; then
if ask_yn "Log in to Netlify now?" "n"; then netlify login; fi
fi
# ─── Cloudflare Wrangler (Pages + Workers) ───────────────────────────────────
step "Checking Cloudflare Wrangler"
if cmd_exists wrangler; then
ok "Wrangler already installed ($(wrangler --version 2>/dev/null | head -1))"
add_result "Cloudflare Wrangler" "PRESENT" ""
else
info "Wrangler deploys to Cloudflare Pages (static sites) and Workers (edge)."
info "Free tier: unlimited static requests, generous Workers quota, custom domains."
if ask_yn "Install Cloudflare Wrangler?"; then
if cmd_exists npm; then
npm install -g wrangler >> "$LOG_FILE" 2>&1 || true
if cmd_exists wrangler; then
ok "Wrangler installed"
add_result "Cloudflare Wrangler" "INSTALLED" ""
else
warn "Wrangler installed but needs terminal restart for PATH"
add_result "Cloudflare Wrangler" "INSTALLED" "restart terminal"
fi
else
fail "npm not available - install Node.js first"
add_result "Cloudflare Wrangler" "FAILED" "npm missing"
fi
else
add_result "Cloudflare Wrangler" "SKIPPED" ""
fi
fi
info "Cloudflare deploy quickstart:"
tip "wrangler login # browser OAuth"
tip "wrangler pages deploy ./dist --project-name=NAME # static site -> Pages"
tip "wrangler deploy # deploy a Worker (edge)"
tip "Headless/CI: export CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID"
if cmd_exists wrangler; then
if ask_yn "Log in to Cloudflare now?" "n"; then wrangler login; fi
fi
step "Checking Vercel CLI"
if cmd_exists vercel; then
ok "Vercel CLI already installed"
add_result "Vercel CLI" "PRESENT" ""
else
if ask_yn "Install Vercel CLI?" "n"; then
if cmd_exists npm; then
npm install -g vercel >> "$LOG_FILE" 2>&1 || true
cmd_exists vercel && ok "Vercel CLI installed" && add_result "Vercel CLI" "INSTALLED" "" || add_result "Vercel CLI" "FAILED" ""
else
fail "npm not available"
add_result "Vercel CLI" "FAILED" "npm missing"
fi
else
add_result "Vercel CLI" "SKIPPED" ""
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 4: ENVIRONMENT HEALTH CHECK
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "Validation & Health Check"
section "PHASE 4: Environment Health Check"
echo ""
step "Running functional validation tests"
if cmd_exists node; then
NODE_TEST="$(node -e "console.log('node-ok')" 2>/dev/null || true)"
[[ "$NODE_TEST" == *node-ok* ]] && ok "Node.js exec: PASS" || warn "Node.js exec: FAIL"
fi
if [ -n "${PYTHON_CMD:-}" ]; then
PY_SSL="$($PYTHON_CMD -c "import ssl; print('py-ssl-ok:', ssl.OPENSSL_VERSION)" 2>/dev/null || true)"
[[ "$PY_SSL" == *py-ssl-ok* ]] && ok "Python SSL: PASS (${PY_SSL#*: })" || warn "Python SSL: FAIL"
fi
if cmd_exists git; then
GIT_LS="$(git ls-remote --heads https://github.com/anthropics/claude-code.git 2>/dev/null || true)"
[ -n "$GIT_LS" ] && ok "Git HTTPS: PASS" || warn "Git HTTPS: FAIL"
fi
echo ""
printf " ${WHITE}%-18s %-10s %-35s${NC}\n" "Component" "Status" "Version / Details"
printf " %-18s %-10s %-35s\n" "──────────────────" "──────────" "───────────────────────────────────"
# Name|command (the command may contain `||` fallbacks for alt binary names)
CHECKS="
Homebrew|brew --version
Git|git --version
Node.js|node --version
npm|npm --version
Python|python3 --version
pipx|pipx --version
uv|uv --version
Claude Code|claude --version
OpenAI Codex|codex --version
ChatGPT CLI|chatgpt --version
GitHub CLI|gh --version
Netlify CLI|netlify --version
Cloudflare Wrangler|wrangler --version
aider|aider --version
llm|llm --version
repomix|repomix --version
ast-grep|ast-grep --version
tldr|tldr --version
httpie|http --version
gron|gron --version
just|just --version
gemini-cli|gemini --version
aichat|aichat --version
ripgrep|rg --version
jq|jq --version
fd|fd --version || fdfind --version
ImageMagick|magick --version || convert --version
ffmpeg|ffmpeg -version
pandoc|pandoc --version
pdftotext|pdftotext -v
tesseract|tesseract --version
yt-dlp|yt-dlp --version
markitdown|markitdown --version
jc|jc -v
bat|bat --version || batcat --version
fzf|fzf --version
shellcheck|shellcheck --version
tmux|tmux -V
sqlite3|sqlite3 --version
"
PASS_COUNT=0
FAIL_COUNT=0
while IFS='|' read -r name cmd; do
[ -z "$name" ] && continue
# Capture stderr too (tesseract/pdftotext print version there) but drop shell
# "command not found" noise (forced English via LC_ALL=C) and runtime errors,
# so a missing/broken tool reads as empty instead of a false PASS.
ver="$(LC_ALL=C bash -c "$cmd" 2>&1 | grep -viE 'command not found|no such file|dyld|library not loaded|symbol not found|abort trap|illegal instruction|killed|traceback' | head -1 || true)"
padded_name="$(printf "%-18s" "$name")"
if [ -n "$ver" ]; then
printf " %s ${GREEN}%-10s${NC} ${GRAY}%s${NC}\n" "$padded_name" "PASS" "${ver:0:35}"
PASS_COUNT=$((PASS_COUNT + 1))
else
printf " %s ${YELLOW}%-10s${NC} ${GRAY}%s${NC}\n" "$padded_name" "—" "not installed / skipped"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
done <<< "$CHECKS"
echo ""
echo -e " ─────────────────────────────────────────────────────────────────"
echo -e " ${GREEN}Installed/Present: $PASS_COUNT${NC} ${GRAY}Not installed: $FAIL_COUNT${NC}"
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 5: QUICK REFERENCE GUIDE
# ═══════════════════════════════════════════════════════════════════════════════
show_progress "Complete"
section "PHASE 5: Quick Reference Guide"
echo -e "${CYAN}"
cat << 'GUIDE'
┌─────────────────────────────────────────────────────────────────┐
│ HOW TO USE YOUR AI TERMINALS (macOS) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CLAUDE CODE claude start a session │
│ claude "fix bug" one-shot │
│ claude doctor diagnose config │
│ OPENAI CODEX codex OpenAI agentic coding │
│ CHATGPT CLI chatgpt quick Q&A │
│ COPILOT CLI gh copilot suggest shell command help │
│ │
│ AI FRAMEWORKS │
│ ───────────── │
│ aider AI pair programmer over your git repo │
│ llm "prompt" one-shot prompt; llm -c to continue │
│ repomix pack a repo into one file for any model │
│ files-to-prompt . turn a folder into a single prompt │
│ gemini Google Gemini in the terminal │
│ aichat chat / REPL / shell assistant │
│ │
│ CONVERSIONS (feed anything to an AI) │
│ ─────────── │
│ markitdown f.docx Office/PDF/images -> Markdown │
│ pdftotext f.pdf - PDF -> text on stdout │
│ tesseract img out OCR an image/scan to text │
│ pandoc a.md -o b.pdf convert between doc formats │
│ jc -p df command output -> JSON │
│ │
│ DEPLOY netlify deploy --prod │
│ │
│ THE MAGIC COMBO │
│ cd /path/to/site && claude │
│ > improve the hero section, then deploy a draft to Netlify │
│ │
│ MCP CONNECTORS (extend Claude Code) │
│ claude mcp add playwright -- npx @playwright/mcp@latest │
│ claude mcp add filesystem -- npx -y \ │
│ @modelcontextprotocol/server-filesystem ~/ │
│ claude mcp list │
│ │
│ TIPS │
│ - cd into your project BEFORE starting claude │
│ - Open a NEW terminal after install so PATH updates load │
│ - Homebrew on Apple Silicon lives in /opt/homebrew │
│ │
└─────────────────────────────────────────────────────────────────┘
GUIDE
echo -e "${NC}"
# ═══════════════════════════════════════════════════════════════════════════════
# FINAL SUMMARY
# ═══════════════════════════════════════════════════════════════════════════════
ELAPSED="$((SECONDS / 60))m$((SECONDS % 60))s"
echo -e " ${GREEN}╔═══════════════════════════════════════════════════════════════════╗${NC}"
echo -e " ${GREEN}║ KICKSTART COMPLETE ║${NC}"
echo -e " ${GREEN}╠═══════════════════════════════════════════════════════════════════╣${NC}"
printf " ${GREEN}║${NC} ${WHITE}%-22s %-12s %-25s${NC} ${GREEN}║${NC}\n" "Component" "Status" "Detail"
printf " ${GREEN}║${NC} %-22s %-12s %-25s ${GREEN}║${NC}\n" "──────────────────────" "────────────" "─────────────────────────"
idx=0
while [ "$idx" -lt "${#RESULT_NAMES[@]}" ]; do
name="${RESULT_NAMES[$idx]}"
status="${RESULT_STATUS[$idx]}"
detail="${RESULT_DETAIL[$idx]:0:25}"
case "$status" in
INSTALLED|PRESENT|OK) color="$GREEN" ;;
SKIPPED|PARTIAL|TRIGGERED) color="$YELLOW" ;;
FAILED|ERROR) color="$RED" ;;
*) color="$GRAY" ;;
esac
printf " ${GREEN}║${NC} ${color}%-22s %-12s${NC} ${GRAY}%-25s${NC} ${GREEN}║${NC}\n" "$name" "$status" "$detail"
idx=$((idx + 1))
done
echo -e " ${GREEN}║ ║${NC}"
echo -e " ${GREEN}║ Time elapsed: $ELAPSED$(printf '%*s' $((52 - ${#ELAPSED})) '')║${NC}"
echo -e " ${GREEN}║ ║${NC}"
echo -e " ${GREEN}║ NEXT STEPS: ║${NC}"
echo -e " ${GREEN}║ 1. Open a NEW terminal (or: source your shell profile) ║${NC}"
echo -e " ${GREEN}║ 2. Run 'claude' to start Claude Code (browser auth first time) ║${NC}"
echo -e " ${GREEN}║ 3. Run 'gh auth login' for GitHub Copilot ║${NC}"
echo -e " ${GREEN}║ 4. Run 'netlify login' to authenticate Netlify ║${NC}"
echo -e " ${GREEN}║ 5. cd into your project folder, then 'claude' to start! ║${NC}"
echo -e " ${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e " ${GRAY}Profile updated: $PROFILE_FILE · Log: $LOG_FILE${NC}"
echo ""
Full PowerShell script
Expand Start-AITerminalKickstart.ps1 — Windows / PowerShell edition
#Requires -Version 5.1
<#
.SYNOPSIS
AI Terminal Kickstart - One script to prep any Windows PC for AI-powered terminal work.
.DESCRIPTION
Interactive, menu-driven installer that bootstraps a Windows PC with everything
needed for the top 3 AI terminal solutions:
1. Claude Code (Anthropic) - Terminal AI coding assistant
2. ChatGPT CLI (OpenAI) - Interactive GPT in your terminal
3. GitHub Copilot CLI (GitHub) - AI-powered shell suggestions
The script:
- Self-upgrades to PowerShell 7 if running from PS 5.x
- Installs all prerequisites (Git, Node.js, Python, etc.)
- Runs your existing installer scripts as sub-modules
- Provides a guided, color-coded experience any junior tech can follow
- Validates every install step with logic tests
- Generates a final health-check report
.PARAMETER AutoMode
Skip all menus and install everything. Great for unattended setup.
NOTE: -AutoMode (and -All) install US-operated tooling only. They do NOT
install Kimi - that is opt-in via -WithKimi (see below).
.PARAMETER WithKimi
OPT-IN. Additionally install the Kimi Code CLI (the standalone 'kimi'
terminal) from Moonshot AI. Default is OFF and it is deliberately NOT
pulled in by -AutoMode/-All, because Moonshot AI is a China-operated
company and neither endpoint (api.moonshot.ai global, api.moonshot.cn
mainland) publishes a Western data-residency or zero-retention guarantee.
Users who want US-only models leave this off (the default) and Kimi is
skipped entirely.
.PARAMETER SkipPowerShell7
Don't attempt to upgrade PowerShell (stay on 5.x).
.PARAMETER ScriptsDir
Directory containing your helper PS1 scripts. Defaults to the same folder as this script.
.EXAMPLE
.\Start-AITerminalKickstart.ps1
# Interactive menu - pick what you want
.EXAMPLE
.\Start-AITerminalKickstart.ps1 -AutoMode
# Install everything (US-operated stack only), no prompts (except API keys)
.EXAMPLE
.\Start-AITerminalKickstart.ps1 -AutoMode -WithKimi
# Everything PLUS the China-operated Kimi Code CLI (explicit opt-in)
.NOTES
Author : AI Terminal ops
Date : 2026-03-25
Tested : Windows Server 2022, Windows 10/11, PowerShell 5.1+
US-only by default: without -WithKimi, no China-operated tooling is
installed. Kimi (Moonshot AI) is the only opt-in addition and never rides
along with -AutoMode/-All. See -WithKimi above for the data-residency caveat.
Known issues + manual workarounds (Server 2022 / Win 11):
1. Chocolatey install fails with:
"Cannot overwrite variable result because the variable has been optimized."
Cause: Chocolatey's install.ps1 was written for Windows PowerShell 5.1
and trips a PowerShell 7+ variable-optimization bug.
Auto-fix in this script: detects PS 7+ and shells out to powershell.exe (5.1).
Manual fallback (run from elevated Windows PowerShell 5.1):
Set-ExecutionPolicy Bypass -Scope Process -Force; `
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; `
iex ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
From pwsh 7+:
& "$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile `
-ExecutionPolicy Bypass -Command "<the line above>"
2. Python "not found after install":
Cause: Installer succeeded but $env:Path in the current shell doesn't see
the new install dir yet (e.g. C:\Python312 for InstallAllUsers=1,
or C:\Program Files\Python312 for winget system scope).
Auto-fix in this script: Refresh-Path covers all common locations and the
Python install falls back to the direct python.org installer.
Manual fallback (refresh in current session):
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + `
[Environment]::GetEnvironmentVariable('Path','User')
Manual install (direct python.org, no winget/choco needed):
$v='3.12.13'; $u="https://www.python.org/ftp/python/$v/python-$v-amd64.exe"; `
$f="$env:TEMP\python-$v-amd64.exe"; iwr $u -OutFile $f; `
Start-Process $f -ArgumentList '/quiet','InstallAllUsers=1','PrependPath=1' -Wait
#>
[CmdletBinding()]
param(
[switch]$AutoMode,
[switch]$All,
[switch]$WithKimi,
[switch]$SkipPowerShell7,
[string]$ScriptsDir = ""
)
# ═══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════════
$ErrorActionPreference = 'Continue'
Set-StrictMode -Version Latest
if (-not $ScriptsDir) {
$ScriptsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
if (-not $ScriptsDir) { $ScriptsDir = Get-Location }
}
$script:TempDir = Join-Path $env:TEMP "ai-kickstart-$(Get-Date -Format 'yyyyMMdd')"
$script:LogFile = Join-Path $script:TempDir "kickstart.log"
$script:Results = [System.Collections.ArrayList]::new()
$script:StartTime = Get-Date
# ═══════════════════════════════════════════════════════════════════════════════
# HELPER FUNCTIONS
# ═══════════════════════════════════════════════════════════════════════════════
function Write-Banner {
$banner = @"
╔═══════════════════════════════════════════════════════════════════╗
║ ║
║ A I T E R M I N A L K I C K S T A R T ║
║ ───────────────────────────────────────── ║
║ One Script To Prep Them All ║
║ ║
║ Claude Code | ChatGPT CLI | GitHub Copilot CLI ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝
"@
Write-Host $banner -ForegroundColor Magenta
}
function Write-Section {
param([string]$Title)
$line = "=" * 65
Write-Host ""
Write-Host " $line" -ForegroundColor DarkCyan
Write-Host " $Title" -ForegroundColor Cyan
Write-Host " $line" -ForegroundColor DarkCyan
}
function Write-Step {
param([string]$Msg)
Write-Host "`n >> $Msg" -ForegroundColor Cyan
Add-Content -Path $script:LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] STEP: $Msg" -ErrorAction SilentlyContinue
}
function Write-OK {
param([string]$Msg)
Write-Host " [OK] $Msg" -ForegroundColor Green
Add-Content -Path $script:LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] OK: $Msg" -ErrorAction SilentlyContinue
}
function Write-Warn {
param([string]$Msg)
Write-Host " [WARN] $Msg" -ForegroundColor Yellow
Add-Content -Path $script:LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] WARN: $Msg" -ErrorAction SilentlyContinue
}
function Write-Fail {
param([string]$Msg)
Write-Host " [FAIL] $Msg" -ForegroundColor Red
Add-Content -Path $script:LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] FAIL: $Msg" -ErrorAction SilentlyContinue
}
function Write-Info {
param([string]$Msg)
Write-Host " $Msg" -ForegroundColor Gray
}
function Write-Tip {
param([string]$Msg)
Write-Host " [TIP] $Msg" -ForegroundColor DarkYellow
}
function Add-Result {
param([string]$Component, [string]$Status, [string]$Detail = "")
[void]$script:Results.Add([PSCustomObject]@{
Component = $Component
Status = $Status
Detail = $Detail
Time = Get-Date -Format 'HH:mm:ss'
})
}
function Test-CmdExists {
param([string]$Name)
$null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
}
function Refresh-Path {
# Rebuild from registry but PRESERVE any paths added during this session
$machine = [Environment]::GetEnvironmentVariable('Path', 'Machine')
$user = [Environment]::GetEnvironmentVariable('Path', 'User')
$basePath = "$machine;$user"
# Preserve session-added paths that aren't in the registry yet
$currentParts = $env:Path -split ';' | Where-Object { $_ -ne '' }
$baseParts = $basePath -split ';' | Where-Object { $_ -ne '' }
$sessionOnly = $currentParts | Where-Object { $_ -notin $baseParts }
$env:Path = $basePath
foreach ($sp in $sessionOnly) {
if ($env:Path -notlike "*$sp*") { $env:Path += ";$sp" }
}
# Also add common tool paths that might not be registered yet.
# NOTE: python.org "InstallAllUsers=1" lands at C:\Python3xx (no Programs path),
# winget for Python.Python.3.xx -e on system scope lands at C:\Program Files\Python3xx.
# Per-user (default) winget lands at $env:LOCALAPPDATA\Programs\Python\Python3xx.
$extras = @(
"$env:ProgramFiles\Git\cmd",
"$env:ProgramFiles\Git\bin",
"$env:ProgramFiles\nodejs",
"$env:USERPROFILE\.local\bin",
"$env:USERPROFILE\.cargo\bin",
"$env:APPDATA\npm",
"$env:LOCALAPPDATA\Programs\Python\Python312",
"$env:LOCALAPPDATA\Programs\Python\Python312\Scripts",
"$env:LOCALAPPDATA\Programs\Python\Python313",
"$env:LOCALAPPDATA\Programs\Python\Python313\Scripts",
"C:\Python312",
"C:\Python312\Scripts",
"C:\Python313",
"C:\Python313\Scripts",
"$env:ProgramFiles\Python312",
"$env:ProgramFiles\Python312\Scripts",
"$env:ProgramFiles\Python313",
"$env:ProgramFiles\Python313\Scripts",
"${env:ProgramFiles(x86)}\Python312",
"${env:ProgramFiles(x86)}\Python312\Scripts"
)
foreach ($p in $extras) {
if ((Test-Path $p) -and $env:Path -notlike "*$p*") {
$env:Path += ";$p"
}
}
}
function Test-IsAdmin {
([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Test-IsPwsh7Plus {
# PowerShell 7+ (Core) trips a known optimization bug in the Chocolatey
# install.ps1 -> "Cannot overwrite variable result because the variable
# has been optimized." Detect so we can shell out to Windows PowerShell 5.1.
$PSVersionTable.PSEdition -eq 'Core' -and $PSVersionTable.PSVersion.Major -ge 7
}
function Get-WindowsPowerShell51 {
# Returns the path to powershell.exe (Windows PowerShell 5.1) if present.
$candidate = Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe'
if (Test-Path $candidate) { return $candidate }
$cmd = Get-Command powershell.exe -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
return $null
}
function Get-LatestPythonPatch {
# Scrape https://www.python.org/ftp/python/ for the highest published
# patch (e.g. 3.12.10) under the requested major.minor (e.g. 3.12).
# Returns $null on any failure so callers can fall back to a pinned version.
param([string]$MajorMinor = '3.12')
try {
$ProgressPreference = 'SilentlyContinue'
$resp = Invoke-WebRequest -Uri 'https://www.python.org/ftp/python/' -UseBasicParsing -TimeoutSec 30
$escMM = [regex]::Escape($MajorMinor)
# Match "3.12.<digits>/" only - excludes alpha/beta/rc tags
$pattern = ">($escMM\.\d+)/<"
$matches = [regex]::Matches($resp.Content, $pattern)
if (-not $matches.Count) { return $null }
$versions = $matches | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique
$sorted = $versions | Sort-Object { [version]$_ } -Descending
return $sorted[0]
}
catch { return $null }
}
function Resolve-PythonInstallerUrl {
# Returns @{ Version = '3.12.x'; Url = '...' } for the python.org direct
# installer fallback. Tries auto-detection first, then the pinned
# $script:PythonFullVersion as a safety net.
param(
[string]$MajorMinor = $script:PythonVersion,
[string]$Pinned = $script:PythonFullVersion
)
$ver = Get-LatestPythonPatch -MajorMinor $MajorMinor
if (-not $ver) { $ver = $Pinned }
if (-not $ver) { return $null }
@{
Version = $ver
Url = "https://www.python.org/ftp/python/$ver/python-$ver-amd64.exe"
}
}
function Show-Progress {
param(
[string]$Activity,
[string]$Status,
[int]$Step,
[int]$TotalSteps
)
$pct = [math]::Round(($Step / $TotalSteps) * 100)
$barLen = 30
$filled = [math]::Round($barLen * $Step / $TotalSteps)
$empty = $barLen - $filled
$bar = "$([char]0x2588)" * $filled + "$([char]0x2591)" * $empty
Write-Host "`r [$bar] $pct% - $Status " -NoNewline -ForegroundColor Cyan
Write-Progress -Activity $Activity -Status "$Status ($pct%)" -PercentComplete $pct
}
function Complete-Progress {
param([string]$Activity)
Write-Progress -Activity $Activity -Completed
Write-Host "" # newline after progress bar
}
function Invoke-Download {
# Download with progress display, retry on failure
param(
[string]$Uri,
[string]$OutFile,
[string]$Description = "Downloading",
[int]$MaxRetries = 2
)
$attempt = 0
while ($attempt -le $MaxRetries) {
try {
Write-Info "$Description (attempt $($attempt + 1))..."
$ProgressPreference = 'SilentlyContinue' # Invoke-WebRequest's built-in progress is slow
Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing -TimeoutSec 120
$ProgressPreference = 'Continue'
$sizeKB = [math]::Round((Get-Item $OutFile).Length / 1KB)
Write-OK "$Description complete (${sizeKB} KB)"
return $true
}
catch {
$attempt++
if ($attempt -gt $MaxRetries) {
Write-Fail "$Description failed after $($MaxRetries + 1) attempts: $_"
return $false
}
Write-Warn "Download failed, retrying in 3 seconds... ($_)"
Start-Sleep -Seconds 3
}
}
return $false
}
# Version-flexible package names (auto-detect latest where possible)
$script:PythonVersion = "3.12" # Change this single value to update Python target
$script:PythonFullVersion = "3.12.13" # Safety-net pin for python.org direct-download fallback (auto-detector tries the latest first)
$script:PythonWingetId = "Python.Python.$($script:PythonVersion)"
$script:PythonChocoId = "python$($script:PythonVersion -replace '\.','')"
$script:NodeMajor = "22" # Change this to update Node.js LTS target
# Phase tracking for progress bar
$script:TotalPhases = 6
$script:CurrentPhase = 0
function Enter-Phase {
param([string]$Name)
$script:CurrentPhase++
Show-Progress -Activity "AI Terminal Kickstart" -Status "Phase $($script:CurrentPhase)/$($script:TotalPhases): $Name" -Step $script:CurrentPhase -TotalSteps $script:TotalPhases
}
function Get-UserChoice {
param(
[string]$Prompt,
[string[]]$Options,
[int]$Default = 0
)
if ($AutoMode) { return $Default }
Write-Host ""
for ($i = 0; $i -lt $Options.Count; $i++) {
$marker = if ($i -eq $Default) { "*" } else { " " }
$color = if ($i -eq $Default) { "White" } else { "Gray" }
Write-Host " [$($i+1)]$marker $($Options[$i])" -ForegroundColor $color
}
Write-Host ""
Write-Host " $Prompt [$($Default+1)]: " -NoNewline -ForegroundColor Yellow
$input = Read-Host
if ($input -match '^\d+$') {
$idx = [int]$input - 1
if ($idx -ge 0 -and $idx -lt $Options.Count) { return $idx }
}
return $Default
}
function Get-YesNo {
param([string]$Prompt, [bool]$Default = $true)
if ($AutoMode) { return $Default }
$hint = if ($Default) { "Y/n" } else { "y/N" }
Write-Host " $Prompt ($hint): " -NoNewline -ForegroundColor Yellow
$answer = Read-Host
if ([string]::IsNullOrWhiteSpace($answer)) { return $Default }
return ($answer -match '^[Yy]')
}
function Invoke-SubScript {
param(
[string]$Name,
[string]$FileName,
[hashtable]$Params = @{}
)
$path = Join-Path $ScriptsDir $FileName
if (-not (Test-Path $path)) {
Write-Warn "Script not found: $path"
Write-Info "Skipping $Name - file missing from scripts directory"
Add-Result $Name "SKIPPED" "Script file not found"
return $false
}
Write-Info "Running: $FileName"
try {
& $path @Params
Add-Result $Name "RAN" "Sub-script executed"
return $true
}
catch {
Write-Fail "$Name script error: $_"
Add-Result $Name "ERROR" "$_"
return $false
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 0 - INITIALIZATION
# ═══════════════════════════════════════════════════════════════════════════════
if (-not (Test-Path $script:TempDir)) {
New-Item -Path $script:TempDir -ItemType Directory -Force | Out-Null
}
Write-Banner
# Show system info
$osInfo = Get-CimInstance Win32_OperatingSystem
$psVer = $PSVersionTable.PSVersion
$isAdmin = Test-IsAdmin
Write-Host " System Info:" -ForegroundColor White
Write-Host " OS : $($osInfo.Caption)" -ForegroundColor Gray
Write-Host " Build : $($osInfo.BuildNumber)" -ForegroundColor Gray
Write-Host " PowerShell : $psVer" -ForegroundColor Gray
Write-Host " Admin : $isAdmin" -ForegroundColor $(if ($isAdmin) { 'Green' } else { 'Yellow' })
Write-Host " Scripts Dir : $ScriptsDir" -ForegroundColor Gray
Write-Host " Log File : $($script:LogFile)" -ForegroundColor Gray
Write-Host ""
if (-not $isAdmin) {
Write-Host " ┌──────────────────────────────────────────────────────────────┐" -ForegroundColor Yellow
Write-Host " │ WARNING: Not running as Administrator. │" -ForegroundColor Yellow
Write-Host " │ Some installations may fail or prompt UAC. │" -ForegroundColor Yellow
Write-Host " │ Right-click PowerShell > Run as Administrator for best │" -ForegroundColor Yellow
Write-Host " │ results. │" -ForegroundColor Yellow
Write-Host " └──────────────────────────────────────────────────────────────┘" -ForegroundColor Yellow
Write-Host ""
}
# ─── Pre-flight: TLS, Network, Disk, Admin, Repos ─────────────────────────────
Enter-Phase "Pre-flight Checks"
if ($All) { $AutoMode = $true }
if (-not $AutoMode) {
$modeChoice = Get-UserChoice -Prompt "Install mode" -Options @(
"Everything - install the full recommended stack, unattended",
"Choose - ask before each component"
) -Default 1
if ($modeChoice -eq 0) { $AutoMode = $true; Write-OK "Mode: install everything (unattended)." }
}
Write-Section "PRE-FLIGHT CHECKS"
# Force TLS 1.2+ for all web requests in this session
Write-Step "Enforcing TLS 1.2+ for all downloads"
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13
Write-OK "TLS 1.2/1.3 enforced for all Invoke-WebRequest/RestMethod calls"
Add-Result "TLS Security" "OK" "TLS 1.2+"
# Admin access validation
Write-Step "Checking administrator privileges"
if ($isAdmin) {
Write-OK "Running as Administrator - full install capability"
Add-Result "Admin Access" "OK" "Elevated"
}
else {
Write-Warn "NOT running as Administrator"
Write-Info "The following will fail without admin: Chocolatey, execution policy,"
Write-Info "system PATH changes, some MSI installers."
Write-Info "Recommendation: Close and re-run as Administrator."
Add-Result "Admin Access" "WARN" "Not elevated"
}
# Network connectivity - test multiple download sources
Write-Step "Testing internet connectivity to download sources"
$script:NetworkOK = $true
$endpoints = @(
@{ Name = "GitHub API"; URL = "https://api.github.com" },
@{ Name = "Node.js"; URL = "https://nodejs.org/dist/index.json" },
@{ Name = "Claude installer"; URL = "https://claude.ai" },
@{ Name = "Chocolatey"; URL = "https://community.chocolatey.org" },
@{ Name = "npm registry"; URL = "https://registry.npmjs.org" },
@{ Name = "PyPI"; URL = "https://pypi.org" }
)
$reachable = 0
$unreachable = 0
foreach ($ep in $endpoints) {
try {
$null = Invoke-WebRequest -Uri $ep.URL -UseBasicParsing -TimeoutSec 8 -Method Head -ErrorAction Stop
Write-OK "$($ep.Name) reachable"
$reachable++
}
catch {
Write-Warn "$($ep.Name) ($($ep.URL)) - UNREACHABLE"
$unreachable++
}
}
if ($unreachable -gt 0) {
Write-Warn "$unreachable of $($endpoints.Count) download sources unreachable."
Write-Info "Check your network, proxy, or firewall settings."
if ($unreachable -ge 4) {
Write-Fail "Most download sources are unreachable. Script will likely fail."
$script:NetworkOK = $false
}
}
else {
Write-OK "All $reachable download sources reachable"
}
Add-Result "Network" $(if ($unreachable -eq 0) { "OK" } elseif ($unreachable -lt 4) { "PARTIAL" } else { "FAILED" }) "$reachable/$($endpoints.Count) reachable"
# Disk space check
Write-Step "Checking available disk space"
try {
$sysDrive = (Get-Item $env:SystemRoot).PSDrive
$freeGB = [math]::Round(($sysDrive.Free / 1GB), 1)
$totalGB = [math]::Round(($sysDrive.Used + $sysDrive.Free) / 1GB, 1)
if ($freeGB -lt 2) {
Write-Fail "CRITICAL: Only $freeGB GB free on $($sysDrive.Name): ($totalGB GB total). Need at least 2 GB."
Write-Info "Free up disk space before continuing."
Add-Result "Disk Space" "FAILED" "$freeGB GB free"
}
elseif ($freeGB -lt 5) {
Write-Warn "Low: $freeGB GB free on $($sysDrive.Name): ($totalGB GB total). Recommend 5+ GB."
Add-Result "Disk Space" "WARN" "$freeGB GB free"
}
else {
Write-OK "Disk space: $freeGB GB free on $($sysDrive.Name): ($totalGB GB total)"
Add-Result "Disk Space" "OK" "$freeGB GB free"
}
}
catch {
# Fallback for non-standard PSDrive
Write-Warn "Could not determine disk space: $_"
Add-Result "Disk Space" "UNKNOWN" ""
}
# Log session separator
Add-Content -Path $script:LogFile -Value "`n$('=' * 70)" -ErrorAction SilentlyContinue
Add-Content -Path $script:LogFile -Value "Session started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ErrorAction SilentlyContinue
Add-Content -Path $script:LogFile -Value "OS: $($osInfo.Caption) Build $($osInfo.BuildNumber) | PS: $psVer | Admin: $isAdmin" -ErrorAction SilentlyContinue
Add-Content -Path $script:LogFile -Value "$('=' * 70)" -ErrorAction SilentlyContinue
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 1 - POWERSHELL 7 CHECK / UPGRADE
# ═══════════════════════════════════════════════════════════════════════════════
Enter-Phase "PowerShell & Core Prerequisites"
Write-Section "PHASE 1: PowerShell Version Check"
if ($psVer.Major -ge 7) {
Write-OK "Already running PowerShell $psVer - no upgrade needed"
Add-Result "PowerShell 7" "PRESENT" "v$psVer"
}
elseif ($SkipPowerShell7) {
Write-Warn "PowerShell 7 upgrade skipped (--SkipPowerShell7 flag)"
Add-Result "PowerShell 7" "SKIPPED" "User opted out"
}
else {
Write-Step "You are on PowerShell $psVer (Windows built-in)"
Write-Info "PowerShell 7 is recommended for AI terminals. It offers:"
Write-Info " - Better performance and modern language features"
Write-Info " - Native JSON/REST support improvements"
Write-Info " - Cross-platform compatibility"
Write-Info " - Required by some advanced Claude Code features"
Write-Host ""
if (Get-YesNo "Install PowerShell 7?") {
Write-Step "Installing PowerShell 7..."
$installed = $false
# Try winget first
if (Test-CmdExists 'winget') {
Write-Info "Using winget to install PowerShell 7..."
try {
$result = winget install --id Microsoft.PowerShell --accept-source-agreements --accept-package-agreements --silent 2>&1
if ($LASTEXITCODE -eq 0 -or ($result -match 'already installed')) {
$installed = $true
Write-OK "PowerShell 7 installed via winget"
}
}
catch { Write-Info "winget method failed, trying MSI..." }
}
# Fallback to MSI download
if (-not $installed) {
Write-Info "Downloading PowerShell 7 MSI from GitHub..."
try {
$releases = Invoke-RestMethod 'https://api.github.com/repos/PowerShell/PowerShell/releases/latest'
$msiAsset = $releases.assets | Where-Object { $_.name -match 'win-x64\.msi$' } | Select-Object -First 1
if ($msiAsset) {
$msiPath = Join-Path $script:TempDir $msiAsset.name
Invoke-WebRequest -Uri $msiAsset.browser_download_url -OutFile $msiPath -UseBasicParsing
Write-OK "Downloaded: $($msiAsset.name)"
Write-Info "Installing silently..."
$msiArgs = "/i `"$msiPath`" /qn /norestart ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1 ENABLE_PSREMOTING=0 REGISTER_MANIFEST=1 USE_MU=1 ENABLE_MU=1"
Start-Process msiexec.exe -ArgumentList $msiArgs -Wait -NoNewWindow
$installed = $true
Write-OK "PowerShell 7 installed via MSI"
}
}
catch {
Write-Fail "MSI download/install failed: $_"
}
}
if ($installed) {
Add-Result "PowerShell 7" "INSTALLED" "Restart terminal to use pwsh.exe"
Write-Host ""
Write-Tip "After this script finishes, open a NEW terminal and type 'pwsh' to use PS7."
Write-Tip "You can also right-click > 'Open in Terminal' for Windows Terminal with PS7."
}
else {
Add-Result "PowerShell 7" "FAILED" "Manual install needed"
Write-Warn "Install manually from: https://aka.ms/powershell-release?tag=stable"
}
}
else {
Write-Info "Staying on PowerShell $psVer"
Add-Result "PowerShell 7" "SKIPPED" "User declined"
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 2 - CORE PREREQUISITES
# ═══════════════════════════════════════════════════════════════════════════════
Write-Section "PHASE 2: Core Prerequisites"
# ─── 2a. Execution Policy ─────────────────────────────────────────────────────
Write-Step "Checking execution policy"
$policy = Get-ExecutionPolicy -Scope LocalMachine
if ($policy -in 'Restricted', 'AllSigned') {
if ($isAdmin) {
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine -Force
Write-OK "Execution policy set to RemoteSigned"
}
else {
Write-Warn "Execution policy is '$policy' - run as Admin to change it"
}
}
else {
Write-OK "Execution policy is '$policy'"
}
Add-Result "Execution Policy" "OK" $policy
# ─── 2b. Package Manager (Chocolatey) ─────────────────────────────────────────
Write-Step "Checking Chocolatey package manager"
if (Test-CmdExists 'choco') {
$chocoVer = choco --version 2>$null
Write-OK "Chocolatey already installed ($chocoVer)"
Add-Result "Chocolatey" "PRESENT" "v$chocoVer"
}
else {
Write-Info "Chocolatey is used to install many developer tools automatically."
if (Get-YesNo "Install Chocolatey?") {
$chocoInstalled = $false
$chocoErr = $null
# Chocolatey's install.ps1 trips a PowerShell 7+ optimization bug
# ("Cannot overwrite variable result because the variable has been
# optimized.") - so when running under pwsh 7+, we shell out to
# Windows PowerShell 5.1 which the script was authored against.
if (Test-IsPwsh7Plus) {
$ps5 = Get-WindowsPowerShell51
if ($ps5) {
Write-Info "Detected PowerShell 7+ - running Chocolatey installer via Windows PowerShell 5.1..."
try {
$ps5Cmd = @"
Set-ExecutionPolicy Bypass -Scope Process -Force
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
"@
& $ps5 -NoProfile -ExecutionPolicy Bypass -Command $ps5Cmd
if ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq $null) { $chocoInstalled = $true }
}
catch { $chocoErr = $_ }
}
else {
Write-Warn "Windows PowerShell 5.1 (powershell.exe) not found - falling back to in-process install"
}
}
# In-process install (Windows PowerShell 5.1, or fallback if PS 5.1 was missing)
if (-not $chocoInstalled) {
try {
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
$chocoInstalled = $true
}
catch { $chocoErr = $_ }
}
Refresh-Path
if ($chocoInstalled -and (Test-CmdExists 'choco')) {
choco feature enable -n allowGlobalConfirmation 2>$null | Out-Null
Write-OK "Chocolatey installed"
Add-Result "Chocolatey" "INSTALLED" ""
}
else {
Write-Fail "Chocolatey install failed: $chocoErr"
Write-Tip "Manual one-liner workaround (run in an elevated Windows PowerShell 5.1, NOT pwsh 7):"
Write-Info " Set-ExecutionPolicy Bypass -Scope Process -Force; [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))"
Write-Tip "Or from pwsh 7+, shell out to Windows PowerShell 5.1:"
Write-Info " & `"`$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe`" -NoProfile -ExecutionPolicy Bypass -Command `"Set-ExecutionPolicy Bypass -Scope Process -Force; [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))`""
Add-Result "Chocolatey" "FAILED" "$chocoErr"
}
}
else {
Add-Result "Chocolatey" "SKIPPED" "User declined"
}
}
# ─── 2c. Git for Windows ──────────────────────────────────────────────────────
Write-Step "Checking Git for Windows"
Refresh-Path
if (Test-CmdExists 'git') {
$gitVer = (git --version) -replace 'git version ', ''
Write-OK "Git already installed ($gitVer)"
Write-Info "Git Bash: $env:ProgramFiles\Git\bin\bash.exe"
Add-Result "Git" "PRESENT" "v$gitVer"
}
else {
Write-Info "Git is REQUIRED for Claude Code and most AI terminal tools."
Write-Info "It also provides Git Bash which Claude Code uses on Windows."
if (Get-YesNo "Install Git for Windows?" $true) {
$gitInstalled = $false
# Try winget first
if (Test-CmdExists 'winget') {
Write-Info "Installing via winget..."
try {
winget install --id Git.Git --accept-source-agreements --accept-package-agreements --silent 2>$null
if ($LASTEXITCODE -eq 0) { $gitInstalled = $true }
}
catch { Write-Info "winget install failed: $_ - trying next method..." }
}
# Try Chocolatey
if (-not $gitInstalled -and (Test-CmdExists 'choco')) {
Write-Info "Installing via Chocolatey..."
try {
choco install git --yes --no-progress
$gitInstalled = $true
}
catch { Write-Info "Chocolatey install failed: $_ - trying direct download..." }
}
# Direct download fallback (auto-detect latest version from GitHub API)
if (-not $gitInstalled) {
Write-Info "Downloading Git from GitHub (auto-detecting latest release)..."
try {
$gitRelease = Invoke-RestMethod 'https://api.github.com/repos/git-for-windows/git/releases/latest'
$gitAsset = $gitRelease.assets | Where-Object { $_.name -match '64-bit\.exe$' -and $_.name -notmatch 'portable' } | Select-Object -First 1
if ($gitAsset) {
$gitInstaller = Join-Path $script:TempDir $gitAsset.name
$dlOk = Invoke-Download -Uri $gitAsset.browser_download_url -OutFile $gitInstaller -Description "Git for Windows ($($gitAsset.name))"
if (-not $dlOk) { throw "Download failed" }
$gitArgs = '/VERYSILENT', '/NORESTART', '/SP-', '/CLOSEAPPLICATIONS'
Start-Process -FilePath $gitInstaller -ArgumentList $gitArgs -Wait -NoNewWindow
$gitInstalled = $true
}
else {
Write-Fail "Could not find Git installer asset in release"
}
}
catch {
Write-Fail "Git download failed: $_"
}
}
Refresh-Path
if (Test-CmdExists 'git') {
Write-OK "Git installed: $(git --version)"
Add-Result "Git" "INSTALLED" (git --version)
}
else {
Write-Fail "Git not found after install attempt"
Add-Result "Git" "FAILED" "Not on PATH"
}
}
else {
Write-Warn "Skipping Git - Claude Code WILL NOT WORK without it!"
Add-Result "Git" "SKIPPED" "User declined (WARNING)"
}
}
# ─── 2d. Node.js ──────────────────────────────────────────────────────────────
Write-Step "Checking Node.js"
Refresh-Path
if (Test-CmdExists 'node') {
$nodeVer = node --version 2>$null
Write-OK "Node.js already installed ($nodeVer)"
if (Test-CmdExists 'npm') {
Write-OK "npm $(npm --version 2>$null)"
}
if (Test-CmdExists 'npx') {
Write-OK "npx available"
}
Add-Result "Node.js" "PRESENT" $nodeVer
}
else {
Write-Info "Node.js is required for Claude Code, npm packages, and MCP servers."
if (Get-YesNo "Install Node.js LTS?" $true) {
$nodeInstalled = $false
if (Test-CmdExists 'winget') {
Write-Info "Installing via winget..."
try {
winget install --id OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent 2>$null
if ($LASTEXITCODE -eq 0) { $nodeInstalled = $true }
}
catch { Write-Info "winget install failed: $_ - trying next method..." }
}
if (-not $nodeInstalled -and (Test-CmdExists 'choco')) {
Write-Info "Installing via Chocolatey..."
try {
choco install nodejs-lts --yes --no-progress
$nodeInstalled = $true
}
catch { Write-Info "Chocolatey install failed: $_ - trying direct download..." }
}
if (-not $nodeInstalled) {
Write-Info "Downloading from nodejs.org..."
try {
$nodeVersions = Invoke-RestMethod 'https://nodejs.org/dist/index.json'
$latestLts = $nodeVersions | Where-Object { $_.lts -ne $false } | Select-Object -First 1
$ver = $latestLts.version
$msi = "node-$ver-x64.msi"
$url = "https://nodejs.org/dist/$ver/$msi"
$installer = Join-Path $script:TempDir $msi
Invoke-WebRequest -Uri $url -OutFile $installer -UseBasicParsing
Start-Process msiexec.exe -ArgumentList "/i `"$installer`" /qn /norestart" -Wait -NoNewWindow
}
catch {
Write-Fail "Node.js download failed: $_"
}
}
Refresh-Path
if (Test-CmdExists 'node') {
Write-OK "Node.js $(node --version 2>$null) installed"
Add-Result "Node.js" "INSTALLED" (node --version 2>$null)
}
else {
Write-Fail "Node.js not found after install"
Add-Result "Node.js" "FAILED" "Not on PATH"
}
}
else {
Add-Result "Node.js" "SKIPPED" "User declined"
}
}
# ─── 2e. Python ───────────────────────────────────────────────────────────────
Write-Step "Checking Python"
Refresh-Path
if (Test-CmdExists 'python') {
$pyVer = python --version 2>&1
Write-OK "Python already installed ($pyVer)"
Add-Result "Python" "PRESENT" "$pyVer"
}
else {
Write-Info "Python is used for MCP server development, data processing,"
Write-Info "security tools, and many Claude Code plugins."
if (Get-YesNo "Install Python $($script:PythonVersion)?" $true) {
$pyInstalled = $false
if (Test-CmdExists 'winget') {
Write-Info "Installing Python $($script:PythonVersion) via winget..."
try {
winget install --id $script:PythonWingetId --accept-source-agreements --accept-package-agreements --silent 2>$null
if ($LASTEXITCODE -eq 0) { $pyInstalled = $true }
}
catch { Write-Info "winget install failed: $_ - trying next method..." }
}
if (-not $pyInstalled -and (Test-CmdExists 'choco')) {
Write-Info "Installing Python $($script:PythonVersion) via Chocolatey..."
try {
choco install $script:PythonChocoId --yes --no-progress
$pyInstalled = $true
}
catch { Write-Info "Chocolatey install failed: $_" }
}
# Direct python.org installer fallback - works on Server 2022 / Win 11
# even when winget and Chocolatey are unavailable.
# The patch version is auto-detected (latest published 3.x.y) so this
# keeps working as Python releases new patches; $script:PythonFullVersion
# is the safety-net pin used if auto-detect fails.
if (-not $pyInstalled) {
$pyTarget = Resolve-PythonInstallerUrl
if ($pyTarget) {
$pyFull = $pyTarget.Version
$pyUrl = $pyTarget.Url
$pyExe = Join-Path $script:TempDir "python-$pyFull-amd64.exe"
Write-Info "Falling back to direct python.org installer ($pyFull)..."
try {
$dlOk = Invoke-Download -Uri $pyUrl -OutFile $pyExe -Description "Python $pyFull installer"
if ($dlOk) {
$pyArgs = '/quiet','InstallAllUsers=1','PrependPath=1','Include_test=0','Include_pip=1','Include_launcher=1'
$proc = Start-Process -FilePath $pyExe -ArgumentList $pyArgs -Wait -PassThru -NoNewWindow
if ($proc.ExitCode -eq 0) { $pyInstalled = $true }
else { Write-Warn "python.org installer exited with code $($proc.ExitCode)" }
}
}
catch { Write-Info "python.org direct install failed: $_" }
}
else {
Write-Warn "Could not resolve a Python $($script:PythonVersion).x installer URL from python.org"
}
}
Refresh-Path
if (Test-CmdExists 'python') {
Write-OK "Python $(python --version 2>&1) installed"
python -m pip install --upgrade pip --quiet 2>$null
Write-OK "pip upgraded"
Add-Result "Python" "INSTALLED" (python --version 2>&1)
}
else {
Write-Fail "Python not found after install"
Write-Tip "PATH not refreshed yet? Open a NEW PowerShell window and re-run, or in this session:"
Write-Info " `$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')"
Write-Tip "Manual install one-liner (direct from python.org, pinned version):"
Write-Info " `$v='3.12.13'; `$u=`"https://www.python.org/ftp/python/`$v/python-`$v-amd64.exe`"; `$f=`"`$env:TEMP\python-`$v-amd64.exe`"; iwr `$u -OutFile `$f; Start-Process `$f -ArgumentList '/quiet','InstallAllUsers=1','PrependPath=1' -Wait"
Add-Result "Python" "FAILED" "Not on PATH"
}
}
else {
Add-Result "Python" "SKIPPED" "User declined"
}
}
# ─── 2f. Additional CLI tools ─────────────────────────────────────────────────
Write-Step "Checking additional CLI tools"
$cliTools = @(
@{ Name = "ripgrep"; Cmd = "rg"; Desc = "Ultra-fast code search (used by Claude Code)" },
@{ Name = "jq"; Cmd = "jq"; Desc = "JSON processor for API responses" },
@{ Name = "fd"; Cmd = "fd"; Desc = "Fast file finder" },
@{ Name = "7zip"; Cmd = "7z"; Desc = "Archive utility" },
@{ Name = "imagemagick"; Cmd = "magick"; Desc = "Image processing (convert, resize, etc.)" },
@{ Name = "ghostscript"; Cmd = "gswin64c"; Desc = "PDF/PostScript engine (required by ImageMagick for PDFs)" },
@{ Name = "ffmpeg"; Cmd = "ffmpeg"; Desc = "Video/audio processing and media conversion" },
@{ Name = "bat"; Cmd = "bat"; Desc = "Syntax-highlighted file viewer (better cat)" },
@{ Name = "fzf"; Cmd = "fzf"; Desc = "Fuzzy finder for files, history, and commands" },
@{ Name = "yq"; Cmd = "yq"; Desc = "YAML processor (like jq but for YAML)" },
@{ Name = "tree"; Cmd = "tree"; Desc = "Directory structure viewer" },
@{ Name = "delta"; Cmd = "delta"; Desc = "Enhanced git diff viewer with syntax highlighting" },
@{ Name = "shellcheck"; Cmd = "shellcheck"; Desc = "Shell script linter and validator" }
)
foreach ($tool in $cliTools) {
Refresh-Path
if (Test-CmdExists $tool.Cmd) {
Write-OK "$($tool.Name) is installed"
Add-Result $tool.Name "PRESENT" ""
}
else {
Write-Info "$($tool.Name): $($tool.Desc)"
if (Get-YesNo "Install $($tool.Name)?") {
if (Test-CmdExists 'choco') {
try {
choco install $tool.Name --yes --no-progress 2>$null
Refresh-Path
if (Test-CmdExists $tool.Cmd) {
Write-OK "$($tool.Name) installed"
Add-Result $tool.Name "INSTALLED" ""
}
else {
Write-Warn "$($tool.Name) installed but not yet on PATH"
Add-Result $tool.Name "INSTALLED" "Restart terminal for PATH"
}
}
catch {
Write-Fail "$($tool.Name) install failed: $_"
Add-Result $tool.Name "FAILED" "$_"
}
}
elseif (Test-CmdExists 'winget') {
try {
$wingetIds = @{
"ripgrep" = "BurntSushi.ripgrep.MSVC"
"jq" = "jqlang.jq"
"fd" = "sharkdp.fd"
"7zip" = "7zip.7zip"
"imagemagick" = "ImageMagick.ImageMagick"
"ghostscript" = "ArtifexSoftware.GhostScript"
"ffmpeg" = "Gyan.FFmpeg"
"bat" = "sharkdp.bat"
"fzf" = "junegunn.fzf"
"yq" = "MikeFarah.yq"
"tree" = ""
"delta" = "dandavison.delta"
"shellcheck" = "koalaman.shellcheck"
}
$id = $wingetIds[$tool.Name]
if ($id) {
winget install --id $id --accept-source-agreements --accept-package-agreements --silent 2>$null
}
Refresh-Path
Add-Result $tool.Name "INSTALLED" "via winget"
}
catch {
Add-Result $tool.Name "FAILED" "$_"
}
}
else {
Write-Warn "No package manager available. Install $($tool.Name) manually."
Add-Result $tool.Name "SKIPPED" "No package manager"
}
}
else {
Add-Result $tool.Name "SKIPPED" "User declined"
}
}
}
# ─── 2g. Python packages ──────────────────────────────────────────────────────
Write-Step "Python package libraries"
if (Test-CmdExists 'python') {
Write-Info "These packages enhance what Claude Code and AI tools can do:"
Write-Info " Data: numpy, pandas, polars, matplotlib, openpyxl"
Write-Info " Web: requests, httpx, beautifulsoup4"
Write-Info " AI: anthropic, openai, fastmcp, mcp"
Write-Info " Dev: pydantic, rich, pyyaml, python-dotenv"
if (Get-YesNo "Install recommended Python packages?") {
$packages = @(
'numpy', 'pandas', 'polars', 'matplotlib', 'openpyxl',
'requests', 'httpx', 'beautifulsoup4', 'lxml',
'anthropic', 'openai', 'fastmcp', 'mcp',
'pydantic', 'rich', 'pyyaml', 'python-dotenv',
'Pillow', 'chardet', 'tabulate', 'jsonlines'
)
try {
$pkgList = $packages -join ' '
python -m pip install --upgrade $pkgList --quiet 2>&1 | Out-Null
Write-OK "Installed $($packages.Count) Python packages"
Add-Result "Python Packages" "INSTALLED" "$($packages.Count) packages"
}
catch {
Write-Warn "Some packages may have failed: $_"
Add-Result "Python Packages" "PARTIAL" "$_"
}
}
else {
Add-Result "Python Packages" "SKIPPED" ""
}
# uv package manager
if (-not (Test-CmdExists 'uv')) {
Write-Info "uv is a fast Python package manager used by MCP servers."
if (Get-YesNo "Install uv?") {
try {
Invoke-RestMethod https://astral.sh/uv/install.ps1 | Invoke-Expression
Refresh-Path
Write-OK "uv installed"
Add-Result "uv" "INSTALLED" ""
}
catch {
Write-Fail "uv install failed: $_"
Add-Result "uv" "FAILED" "$_"
}
}
}
else {
Write-OK "uv already installed"
Add-Result "uv" "PRESENT" ""
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 3 - AI TERMINAL INSTALLATIONS
# ═══════════════════════════════════════════════════════════════════════════════
Enter-Phase "AI Terminal Solutions"
Write-Section "PHASE 3: AI Terminal Solutions"
Write-Host ""
Write-Host " Which AI terminal solutions would you like to set up?" -ForegroundColor White
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────────────────┐" -ForegroundColor DarkCyan
Write-Host " │ 1. Claude Code (Anthropic) │" -ForegroundColor DarkCyan
Write-Host " │ Best for: Deep code understanding, multi-file edits, │" -ForegroundColor Gray
Write-Host " │ agentic coding, MCP servers, security research │" -ForegroundColor Gray
Write-Host " │ Needs: Git Bash, Node.js, Anthropic API key or login │" -ForegroundColor Gray
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ 2. ChatGPT Terminal (OpenAI) │" -ForegroundColor DarkCyan
Write-Host " │ Best for: Quick questions, brainstorming, general AI │" -ForegroundColor Gray
Write-Host " │ chat, streaming responses, session history │" -ForegroundColor Gray
Write-Host " │ Needs: OpenAI API key │" -ForegroundColor Gray
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ 3. GitHub Copilot CLI (GitHub) │" -ForegroundColor DarkCyan
Write-Host " │ Best for: Shell command suggestions, git operations, │" -ForegroundColor Gray
Write-Host " │ explaining commands, GitHub integration │" -ForegroundColor Gray
Write-Host " │ Needs: GitHub account with Copilot subscription │" -ForegroundColor Gray
Write-Host " └──────────────────────────────────────────────────────────────┘" -ForegroundColor DarkCyan
Write-Host ""
$installClaude = Get-YesNo "Install Claude Code?" $true
$installChatGPT = Get-YesNo "Install ChatGPT Terminal?" $true
$installCopilot = Get-YesNo "Install GitHub Copilot CLI?" $true
# ─── 3a. Claude Code ──────────────────────────────────────────────────────────
if ($installClaude) {
Write-Section "Installing Claude Code"
# Check for existing install
Refresh-Path
$claudeExists = (Test-CmdExists 'claude') -or (Test-Path "$env:USERPROFILE\.local\bin\claude.exe")
if ($claudeExists) {
Write-OK "Claude Code already installed"
try {
$ccVer = claude --version 2>$null
Write-OK "Version: $ccVer"
}
catch { Write-Info "Could not retrieve version" }
Add-Result "Claude Code" "PRESENT" ""
}
else {
Write-Step "Installing Claude Code..."
# Method 1: Use existing Install-ClaudeCode.ps1 if available
$ccScript = Join-Path $ScriptsDir "Install-ClaudeCode.ps1"
if (Test-Path $ccScript) {
Write-Info "Found Install-ClaudeCode.ps1 - using your custom installer"
try {
& $ccScript
Refresh-Path
Add-Result "Claude Code" "INSTALLED" "via Install-ClaudeCode.ps1"
}
catch {
Write-Warn "Custom installer had an issue: $_"
Write-Info "Trying the official method..."
}
}
# Method 2: Official native installer
if (-not (Test-CmdExists 'claude')) {
Write-Info "Using official native installer..."
try {
$installScript = Invoke-RestMethod -Uri "https://claude.ai/install.ps1" -UseBasicParsing
Invoke-Expression $installScript
Refresh-Path
}
catch {
Write-Warn "Native installer issue: $_"
}
}
# Method 3: npm fallback
if (-not (Test-CmdExists 'claude') -and (Test-CmdExists 'npm')) {
Write-Info "Trying npm install as fallback..."
try {
npm install -g @anthropic-ai/claude-code 2>&1 | Out-Host
Refresh-Path
}
catch { Write-Info "npm fallback also failed: $_" }
}
# Final check
$env:Path += ";$env:USERPROFILE\.local\bin"
if (Test-CmdExists 'claude') {
Write-OK "Claude Code installed successfully!"
try { Write-OK "Version: $(claude --version 2>$null)" } catch {}
Add-Result "Claude Code" "INSTALLED" ""
}
else {
Write-Fail "Claude Code could not be installed automatically"
Write-Tip "Try manually: irm https://claude.ai/install.ps1 | iex"
Add-Result "Claude Code" "FAILED" "Manual install needed"
}
}
# Run Setup-ClaudeCodeEnv.ps1 if available
$envScript = Join-Path $ScriptsDir "Setup-ClaudeCodeEnv.ps1"
if (Test-Path $envScript) {
Write-Host ""
if (Get-YesNo "Run Setup-ClaudeCodeEnv.ps1 (Python libs, CLI tools, Git config)?") {
Write-Step "Running Claude Code environment setup..."
try {
& $envScript
Add-Result "Claude Env Setup" "RAN" ""
}
catch {
Write-Warn "Env setup error: $_"
Add-Result "Claude Env Setup" "ERROR" "$_"
}
}
}
# Run Install-ClaudePlugins.ps1 if available
$pluginScript = Join-Path $ScriptsDir "Install-ClaudePlugins.ps1"
if (Test-Path $pluginScript) {
Write-Host ""
if (Get-YesNo "Run Install-ClaudePlugins.ps1 (security, frontend, playground, MCP)?") {
Write-Step "Installing Claude Code plugins..."
try {
& $pluginScript
Add-Result "Claude Plugins" "RAN" ""
}
catch {
Write-Warn "Plugin install error: $_"
Add-Result "Claude Plugins" "ERROR" "$_"
}
}
}
# Run Install-PlaygroundPlugin.ps1 if available
$pgScript = Join-Path $ScriptsDir "Install-PlaygroundPlugin.ps1"
if (Test-Path $pgScript) {
Write-Host ""
if (Get-YesNo "Run Install-PlaygroundPlugin.ps1?") {
Write-Step "Installing Playground plugin..."
try {
& $pgScript
Add-Result "Playground Plugin" "RAN" ""
}
catch {
Write-Warn "Playground plugin error: $_"
Add-Result "Playground Plugin" "ERROR" "$_"
}
}
}
# Configure Git Bash path for Claude Code
Write-Step "Configuring Claude Code settings..."
$gitBashExe = "$env:ProgramFiles\Git\bin\bash.exe"
$claudeSettingsDir = Join-Path $env:USERPROFILE ".claude"
$claudeSettingsFile = Join-Path $claudeSettingsDir "settings.json"
if (Test-Path $gitBashExe) {
if (-not (Test-Path $claudeSettingsDir)) {
New-Item -Path $claudeSettingsDir -ItemType Directory -Force | Out-Null
}
$settings = @{}
if (Test-Path $claudeSettingsFile) {
try { $settings = Get-Content $claudeSettingsFile -Raw | ConvertFrom-Json -AsHashtable }
catch { $settings = @{} }
}
if (-not $settings.ContainsKey('env')) { $settings['env'] = @{} }
$settings['env']['CLAUDE_CODE_GIT_BASH_PATH'] = $gitBashExe
$settings | ConvertTo-Json -Depth 10 | Set-Content $claudeSettingsFile -Encoding UTF8
Write-OK "Git Bash path configured in Claude settings"
}
# Git performance tweaks
if (Test-CmdExists 'git') {
git config --global core.fscache true 2>$null
git config --global core.preloadindex true 2>$null
git config --global gc.auto 256 2>$null
Write-OK "Git performance settings applied"
# Check for git user config
$gitUser = git config --global user.name 2>$null
$gitEmail = git config --global user.email 2>$null
if (-not $gitUser -or -not $gitEmail) {
Write-Warn "Git user.name or user.email not set. Git commits will fail without these."
Write-Tip "Run: git config --global user.name 'Your Name'"
Write-Tip "Run: git config --global user.email 'you@example.com'"
}
else {
Write-OK "Git user: $gitUser <$gitEmail>"
}
git config --global init.defaultBranch main 2>$null
}
}
# ─── 3b. ChatGPT Terminal ─────────────────────────────────────────────────────
if ($installChatGPT) {
Write-Section "Installing ChatGPT Terminal"
$chatGPTScript = Join-Path $ScriptsDir "Setup-ChatGPT-Terminal.ps1"
if (Test-Path $chatGPTScript) {
Write-Info "Found Setup-ChatGPT-Terminal.ps1"
Write-Info "This sets up an interactive ChatGPT session right in PowerShell."
Write-Info "Commands: chatgpt (interactive), cgpt 'question' (one-shot), gpt (alias)"
Write-Host ""
Write-Step "Running ChatGPT Terminal setup..."
try {
& $chatGPTScript -InstallOnly
Add-Result "ChatGPT Terminal" "INSTALLED" "Use: chatgpt, cgpt, gpt"
}
catch {
Write-Warn "ChatGPT setup error: $_"
Add-Result "ChatGPT Terminal" "ERROR" "$_"
}
}
else {
Write-Info "Setup-ChatGPT-Terminal.ps1 not found in $ScriptsDir"
Write-Info "Setting up ChatGPT terminal manually..."
# Check for OpenAI API key
if (-not $env:OPENAI_API_KEY) {
Write-Host ""
Write-Host " An OpenAI API key is needed. Get one at: https://platform.openai.com/api-keys" -ForegroundColor Yellow
Write-Host " Enter your OpenAI API key (or press Enter to skip): " -NoNewline -ForegroundColor Yellow
$openaiKey = Read-Host
if ($openaiKey -and $openaiKey.StartsWith('sk-')) {
[System.Environment]::SetEnvironmentVariable('OPENAI_API_KEY', $openaiKey, 'User')
$env:OPENAI_API_KEY = $openaiKey
Write-OK "OPENAI_API_KEY set"
}
}
else {
Write-OK "OPENAI_API_KEY already set"
}
# Install openai-chatgpt-cli if npm is available
if (Test-CmdExists 'npm') {
Write-Info "Installing chatgpt CLI via npm..."
try {
npm install -g chatgpt-cli 2>&1 | Out-Null
Refresh-Path
Write-OK "chatgpt-cli installed"
Add-Result "ChatGPT Terminal" "INSTALLED" "npm chatgpt-cli"
}
catch {
Write-Warn "npm install failed. Use Setup-ChatGPT-Terminal.ps1 instead."
Add-Result "ChatGPT Terminal" "PARTIAL" "Script-based only"
}
}
else {
Add-Result "ChatGPT Terminal" "SKIPPED" "Need npm or setup script"
}
}
}
# ─── 3c. GitHub Copilot CLI ───────────────────────────────────────────────────
if ($installCopilot) {
Write-Section "Installing GitHub Copilot CLI"
Refresh-Path
$ghExists = Test-CmdExists 'gh'
if (-not $ghExists) {
Write-Info "GitHub CLI (gh) is required for Copilot CLI."
if (Get-YesNo "Install GitHub CLI?" $true) {
if (Test-CmdExists 'winget') {
winget install --id GitHub.cli --accept-source-agreements --accept-package-agreements --silent 2>$null
}
elseif (Test-CmdExists 'choco') {
choco install gh --yes --no-progress 2>$null
}
else {
Write-Info "Downloading from GitHub..."
try {
$ghRelease = Invoke-RestMethod 'https://api.github.com/repos/cli/cli/releases/latest'
$ghAsset = $ghRelease.assets | Where-Object { $_.name -match 'windows_amd64\.msi$' } | Select-Object -First 1
if ($ghAsset) {
$ghMsi = Join-Path $script:TempDir $ghAsset.name
Invoke-WebRequest -Uri $ghAsset.browser_download_url -OutFile $ghMsi -UseBasicParsing
Start-Process msiexec.exe -ArgumentList "/i `"$ghMsi`" /qn /norestart" -Wait -NoNewWindow
}
}
catch {
Write-Fail "GitHub CLI download failed: $_"
}
}
Refresh-Path
}
}
if (Test-CmdExists 'gh') {
Write-OK "GitHub CLI installed: $(gh --version 2>$null | Select-Object -First 1)"
# Install Copilot extension
Write-Step "Installing GitHub Copilot CLI extension..."
try {
gh extension install github/gh-copilot 2>&1 | Out-Host
Write-OK "Copilot CLI extension installed"
Write-Info ""
Write-Info "Usage examples:"
Write-Info " gh copilot suggest 'find large files over 100MB'"
Write-Info " gh copilot explain 'git rebase -i HEAD~3'"
Write-Info ""
Write-Tip "You need a GitHub Copilot subscription. Log in with: gh auth login"
Add-Result "GitHub Copilot CLI" "INSTALLED" "gh copilot suggest/explain"
}
catch {
Write-Warn "Copilot extension install error: $_"
Write-Tip "You may need to log in first: gh auth login"
Add-Result "GitHub Copilot CLI" "PARTIAL" "Login may be needed"
}
}
else {
Write-Fail "GitHub CLI not available - Copilot CLI cannot be installed"
Add-Result "GitHub Copilot CLI" "FAILED" "gh CLI missing"
}
}
# ─── 3d. Kimi Code CLI (OPT-IN: -WithKimi, or interactive prompt) ─────────────
# US-only is the DEFAULT. Kimi (Moonshot AI) is China-operated, so it is NEVER
# installed under -AutoMode / -All (Get-YesNo returns its default - here $false -
# when $AutoMode is set). It installs when -WithKimi is passed, OR when an
# interactive operator answers Yes to the prompt below (which defaults to No).
# Leaving the flag off and answering No - or any unattended run - skips Kimi.
if ($WithKimi -or (Get-YesNo "Install Kimi Code CLI (Moonshot AI - China-operated, off by default)?" $false)) {
Write-Section "Installing Kimi Code CLI (opt-in: -WithKimi)"
Refresh-Path
if (Test-CmdExists 'kimi') {
try { $kimiVer = kimi --version 2>$null } catch { $kimiVer = "" }
Write-OK "Kimi Code CLI already installed$(if ($kimiVer) { " ($kimiVer)" })"
Add-Result "Kimi Code CLI" "PRESENT" "$kimiVer"
}
else {
Write-Step "Installing Kimi Code CLI (the standalone 'kimi' terminal)..."
$kimiInstalled = $false
$kimiErr = $null
# Official Windows installer (mirrors the claude.ai / astral.sh pattern).
# macOS/Linux equivalent: curl -LsSf https://code.kimi.com/install.sh | bash
try {
$kimiScript = Invoke-RestMethod -Uri "https://code.kimi.com/install.ps1" -UseBasicParsing
Invoke-Expression $kimiScript
Refresh-Path
if (Test-CmdExists 'kimi') { $kimiInstalled = $true }
}
catch { $kimiErr = $_ ; Write-Warn "Kimi official installer issue: $_" }
# Fallback: uv tool install (Kimi Code is published as a uv-installable tool)
if (-not $kimiInstalled -and (Test-CmdExists 'uv')) {
Write-Info "Trying uv tool fallback..."
try {
uv tool install kimi-cli 2>&1 | Out-Null
Refresh-Path
if (Test-CmdExists 'kimi') { $kimiInstalled = $true }
}
catch { $kimiErr = $_ ; Write-Info "uv fallback also failed: $_" }
}
# Verify with 'kimi --version'
Refresh-Path
if (Test-CmdExists 'kimi') {
$kimiVer = ""
try { $kimiVer = kimi --version 2>$null } catch {}
Write-OK "Kimi Code CLI installed$(if ($kimiVer) { " ($kimiVer)" })"
Add-Result "Kimi Code CLI" "INSTALLED" "$kimiVer"
}
else {
Write-Fail "Kimi Code CLI could not be installed automatically"
Write-Tip "Manual: irm https://code.kimi.com/install.ps1 | iex (or: uv tool install kimi-cli)"
Add-Result "Kimi Code CLI" "FAILED" "$kimiErr"
}
}
# First-launch + API key guidance (never persisted to the environment here)
Write-Host ""
Write-Info "First launch: run 'kimi', then '/login' -> 'Kimi Code' browser OAuth,"
Write-Info "or paste an API key. Keys come from https://platform.kimi.ai"
Write-Info "(Console -> create key). The account needs a >= `$1 recharge to"
Write-Info "activate; the `$1 starter tier is only 3 RPM, so for real agentic"
Write-Info "loops budget the `$10 tier."
# OPTIONAL: use Kimi as a drop-in model INSIDE Claude Code (no separate CLI).
# We only PRINT this recipe - we deliberately do NOT set these variables, so
# leaving them unset keeps Claude Code pointed at US-operated Anthropic models.
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────────────────┐" -ForegroundColor DarkCyan
Write-Host " │ OPTIONAL: Kimi as a drop-in model inside Claude Code │" -ForegroundColor DarkCyan
Write-Host " ├──────────────────────────────────────────────────────────────┤" -ForegroundColor DarkCyan
Write-Host " │ Moonshot ships an Anthropic-compatible endpoint. Set these │" -ForegroundColor Gray
Write-Host " │ THREE env vars in a shell BEFORE launching 'claude' to run │" -ForegroundColor Gray
Write-Host " │ Claude Code against Kimi instead of Anthropic: │" -ForegroundColor Gray
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ `$env:ANTHROPIC_BASE_URL = 'https://api.moonshot.ai/anthropic'" -ForegroundColor White
Write-Host " │ `$env:ANTHROPIC_AUTH_TOKEN = '<your Moonshot key>' │" -ForegroundColor White
Write-Host " │ `$env:ANTHROPIC_MODEL = 'kimi-k2.7-code' │" -ForegroundColor White
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ Models: kimi-k2.7-code (flagship), kimi-k2.6, │" -ForegroundColor Gray
Write-Host " │ kimi-k2-thinking. 256K context. │" -ForegroundColor Gray
Write-Host " └──────────────────────────────────────────────────────────────┘" -ForegroundColor DarkCyan
Write-Host ""
Write-Warn "China-operated caveat: Moonshot AI is a China-operated company."
Write-Info "Both endpoints (api.moonshot.ai global, api.moonshot.cn mainland) are"
Write-Info "China-operated with NO published Western data-residency or zero-retention"
Write-Info "guarantee. Do NOT send confidential client data through Kimi. Leaving the"
Write-Info "three ANTHROPIC_* variables UNSET keeps Claude Code on US-operated models."
}
else {
# Default path (US-only). Mirrors the Gemini-sunset note: documented, no-op.
Write-Info "Skipping Kimi (Moonshot AI) - it is China-operated and off by default. Pass -WithKimi (or answer Yes to the prompt) to opt in."
}
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 3c - AI FRAMEWORKS, CODEX & CONVERSION CLIs
# ═══════════════════════════════════════════════════════════════════════════════
Write-Section "AI Frameworks, Codex & Conversion CLIs"
Write-Info "Agentic helpers (aider, llm), repo/document packers (repomix,"
Write-Info "files-to-prompt, markitdown) and converters (pandoc, poppler, tesseract"
Write-Info "OCR, yt-dlp, jc) that feed code and documents into any AI model."
if (Get-YesNo "Install the AI framework + conversion bundle?") {
# --- npm-based: repomix + OpenAI Codex CLI ---
if (Test-CmdExists 'npm') {
Write-Step "repomix + OpenAI Codex CLI (npm)"
try { npm install -g repomix '@openai/codex' '@ast-grep/cli' tldr 2>&1 | Out-Null } catch { Write-Info "npm install note: $_" }
Refresh-Path
if (Test-CmdExists 'repomix') { Write-OK "repomix installed"; Add-Result "repomix" "INSTALLED" "" }
else { Add-Result "repomix" "SKIPPED" "Restart terminal" }
if (Test-CmdExists 'ast-grep') { Write-OK "ast-grep installed"; Add-Result "ast-grep" "INSTALLED" "" }
else { Add-Result "ast-grep" "SKIPPED" "Restart terminal" }
if (Test-CmdExists 'tldr') { Write-OK "tldr installed"; Add-Result "tldr" "INSTALLED" "" }
else { Add-Result "tldr" "SKIPPED" "Restart terminal" }
if (Test-CmdExists 'codex') { Write-OK "codex installed (run: codex)"; Add-Result "OpenAI Codex" "INSTALLED" "" }
else { Add-Result "OpenAI Codex" "SKIPPED" "Restart terminal" }
Write-Info "Codex on native Windows is experimental; OpenAI recommends WSL2."
Write-Info "Gemini CLI is omitted by default - Google is sunsetting it ~2026-06-18."
}
# --- pip-based standalone Python AI CLIs ---
if (Test-CmdExists 'python') {
Write-Step "aider, llm, files-to-prompt, markitdown, jc (pip)"
try { python -m pip install --upgrade aider-chat llm files-to-prompt 'markitdown[all]' jc httpie --quiet 2>&1 | Out-Null }
catch { Write-Info "pip install note: $_" }
Refresh-Path
foreach ($t in @(
@{ b = 'aider'; n = 'aider' },
@{ b = 'llm'; n = 'llm' },
@{ b = 'files-to-prompt'; n = 'files-to-prompt' },
@{ b = 'markitdown'; n = 'markitdown' },
@{ b = 'jc'; n = 'jc' },
@{ b = 'http'; n = 'httpie' }
)) {
if (Test-CmdExists $t.b) { Write-OK "$($t.n) installed"; Add-Result $t.n "INSTALLED" "" }
else { Add-Result $t.n "SKIPPED" "Restart terminal" }
}
}
# --- Conversion + extra CLIs via choco / winget ---
$aiBundle = @(
@{ Name = 'pandoc'; Cmd = 'pandoc'; Choco = 'pandoc'; Winget = 'JohnMacFarlane.Pandoc'; Desc = 'Universal document converter' },
@{ Name = 'poppler'; Cmd = 'pdftotext'; Choco = 'poppler'; Winget = ''; Desc = 'PDF text extraction (pdftotext)' },
@{ Name = 'tesseract'; Cmd = 'tesseract'; Choco = 'tesseract'; Winget = 'UB-Mannheim.TesseractOCR'; Desc = 'OCR engine (image/scan -> text)' },
@{ Name = 'yt-dlp'; Cmd = 'yt-dlp'; Choco = 'yt-dlp'; Winget = 'yt-dlp.yt-dlp'; Desc = 'Media downloader (pairs with ffmpeg)' },
@{ Name = 'glow'; Cmd = 'glow'; Choco = 'glow'; Winget = 'charmbracelet.glow'; Desc = 'Render Markdown in the terminal' },
@{ Name = 'aichat'; Cmd = 'aichat'; Choco = ''; Winget = 'sigoden.AIChat'; Desc = 'All-in-one LLM CLI' },
@{ Name = 'just'; Cmd = 'just'; Choco = 'just'; Winget = 'Casey.Just'; Desc = 'Command/task runner (justfile)' }
)
foreach ($tool in $aiBundle) {
Refresh-Path
if (Test-CmdExists $tool.Cmd) { Write-OK "$($tool.Name) is installed"; Add-Result $tool.Name "PRESENT" ""; continue }
Write-Info "$($tool.Name): $($tool.Desc)"
$installed = $false
if ($tool.Choco -and (Test-CmdExists 'choco')) {
try { choco install $tool.Choco --yes --no-progress 2>$null; Refresh-Path; if (Test-CmdExists $tool.Cmd) { $installed = $true } } catch {}
}
if (-not $installed -and $tool.Winget -and (Test-CmdExists 'winget')) {
try { winget install --id $tool.Winget --accept-source-agreements --accept-package-agreements --silent 2>$null; Refresh-Path; if (Test-CmdExists $tool.Cmd) { $installed = $true } } catch {}
}
if ($installed) { Write-OK "$($tool.Name) installed"; Add-Result $tool.Name "INSTALLED" "" }
else { Write-Warn "$($tool.Name) not installed (needs choco/winget or a terminal restart)"; Add-Result $tool.Name "SKIPPED" "" }
}
}
else {
Write-Info "Skipped AI framework bundle. Re-run anytime to add it."
}
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 3c2 - AI ECOSYSTEM: MCP SERVERS & ALIGNED REPOS
# ═══════════════════════════════════════════════════════════════════════════════
Write-Section "AI Ecosystem: MCP Servers & Aligned Repos"
if (Test-CmdExists 'claude') {
if (Get-YesNo "Register common MCP servers with Claude Code (user scope)?") {
$aiCfgDir = Join-Path $env:USERPROFILE ".ai-tools"
New-Item -ItemType Directory -Force -Path $aiCfgDir | Out-Null
$memPath = Join-Path $aiCfgDir "memory.json"
# npm/npx servers (Node only). @-scoped package names are single-quoted
# so PowerShell does not treat the leading @ as a splat.
try { claude mcp add --scope user filesystem -- npx -y '@modelcontextprotocol/server-filesystem' "$env:USERPROFILE" 2>&1 | Out-Null } catch {}
try { claude mcp add --scope user memory -e "MEMORY_FILE_PATH=$memPath" -- npx -y '@modelcontextprotocol/server-memory' 2>&1 | Out-Null } catch {}
try { claude mcp add --scope user sequential-thinking -- npx -y '@modelcontextprotocol/server-sequential-thinking' 2>&1 | Out-Null } catch {}
# python/uvx servers (need uv) — fetch + time live on PyPI, not npm
if (Test-CmdExists 'uvx') {
try { claude mcp add --scope user fetch -- uvx mcp-server-fetch 2>&1 | Out-Null } catch {}
try { claude mcp add --scope user time -- uvx mcp-server-time 2>&1 | Out-Null } catch {}
} else {
Write-Info "uvx not found - skipping Python MCP servers (fetch, time). Install uv to add them."
}
try { claude mcp add --scope user playwright -- npx -y '@playwright/mcp@latest' 2>&1 | Out-Null } catch {}
Write-OK "MCP servers registered (run 'claude mcp list' to verify)"
Add-Result "MCP servers" "INSTALLED" "fs,memory,seq,fetch,time,pw"
} else {
Add-Result "MCP servers" "SKIPPED" ""
}
} else {
Write-Info "Claude Code not installed - skipping MCP server registration."
}
Write-Host ""
Write-Info "Aligned GitHub repos worth bookmarking:"
Write-Info " awesome-claude-code https://github.com/hesreallyhim/awesome-claude-code"
Write-Info " awesome-mcp-servers https://github.com/punkpeye/awesome-mcp-servers"
Write-Info " MCP reference servers https://github.com/modelcontextprotocol/servers"
Write-Info " OpenAI Codex CLI https://github.com/openai/codex"
Write-Info " Aider https://github.com/Aider-AI/aider"
if ((Test-CmdExists 'git') -and (Get-YesNo "Clone these reference repos into ~/ai-tools?" $false)) {
$aiReposDir = Join-Path $env:USERPROFILE "ai-tools"
New-Item -ItemType Directory -Force -Path $aiReposDir | Out-Null
$repos = @(
"https://github.com/hesreallyhim/awesome-claude-code.git",
"https://github.com/punkpeye/awesome-mcp-servers.git",
"https://github.com/modelcontextprotocol/servers.git",
"https://github.com/openai/codex.git",
"https://github.com/Aider-AI/aider.git"
)
foreach ($r in $repos) {
$rn = [System.IO.Path]::GetFileNameWithoutExtension($r)
$dest = Join-Path $aiReposDir $rn
if (Test-Path (Join-Path $dest ".git")) { Write-OK "$rn already cloned" }
else {
git clone --depth 1 $r $dest 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) { Write-OK "cloned $rn" } else { Write-Warn "clone failed: $rn" }
}
}
Add-Result "Aligned repos" "INSTALLED" "$aiReposDir"
}
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 3d - NETLIFY CLI & DEPLOYMENT
# ═══════════════════════════════════════════════════════════════════════════════
Enter-Phase "Deployment & Extras"
Write-Section "Netlify CLI & Deployment Tools"
Write-Step "Checking Netlify CLI"
Refresh-Path
if (Test-CmdExists 'netlify') {
$netlifyVer = netlify --version 2>$null
Write-OK "Netlify CLI already installed ($netlifyVer)"
Add-Result "Netlify CLI" "PRESENT" "$netlifyVer"
}
else {
Write-Info "Netlify CLI lets you deploy websites directly from your terminal."
Write-Info "Free tier: 100GB bandwidth, 300 build minutes/month, custom domains."
Write-Info "Works beautifully with Claude Code - AI edits your site, then deploys."
if (Get-YesNo "Install Netlify CLI?" $true) {
if (Test-CmdExists 'npm') {
Write-Info "Installing netlify-cli globally via npm..."
try {
npm install -g netlify-cli 2>&1 | Out-Host
Refresh-Path
if (Test-CmdExists 'netlify') {
Write-OK "Netlify CLI installed ($(netlify --version 2>$null))"
Add-Result "Netlify CLI" "INSTALLED" ""
}
else {
Write-Warn "Netlify CLI installed but restart terminal for PATH"
Add-Result "Netlify CLI" "INSTALLED" "Restart terminal"
}
}
catch {
Write-Fail "Netlify CLI install failed: $_"
Add-Result "Netlify CLI" "FAILED" "$_"
}
}
else {
Write-Fail "npm not available - install Node.js first"
Add-Result "Netlify CLI" "FAILED" "npm missing"
}
}
else {
Add-Result "Netlify CLI" "SKIPPED" ""
}
}
# Netlify authentication guide
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────────────────┐" -ForegroundColor DarkCyan
Write-Host " │ NETLIFY AUTHENTICATION & DEPLOYMENT │" -ForegroundColor DarkCyan
Write-Host " ├──────────────────────────────────────────────────────────────┤" -ForegroundColor DarkCyan
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ LOGIN: │" -ForegroundColor DarkCyan
Write-Host " │ netlify login │" -ForegroundColor White
Write-Host " │ (browser opens > click Authorize > return to terminal) │" -ForegroundColor Gray
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ DEPLOY FROM ANY AI TERMINAL: │" -ForegroundColor DarkCyan
Write-Host " │ cd C:\path\to\my-website │" -ForegroundColor White
Write-Host " │ netlify init # link folder to Netlify site │" -ForegroundColor White
Write-Host " │ netlify deploy # preview draft │" -ForegroundColor White
Write-Host " │ netlify deploy --prod # push live │" -ForegroundColor White
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ AI + DEPLOY (Claude Code): │" -ForegroundColor DarkCyan
Write-Host " │ cd C:\my-site && claude │" -ForegroundColor White
Write-Host " │ > fix the nav menu and deploy a draft to Netlify │" -ForegroundColor White
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ AI + DEPLOY (ChatGPT > manual): │" -ForegroundColor DarkCyan
Write-Host " │ gpt │" -ForegroundColor White
Write-Host " │ You > write me the CSS for a responsive hero section │" -ForegroundColor White
Write-Host " │ (copy output to your file, then: netlify deploy --prod) │" -ForegroundColor Gray
Write-Host " │ │" -ForegroundColor DarkCyan
Write-Host " │ AI + DEPLOY (Copilot CLI): │" -ForegroundColor DarkCyan
Write-Host " │ gh copilot suggest 'deploy this folder to Netlify' │" -ForegroundColor White
Write-Host " └──────────────────────────────────────────────────────────────┘" -ForegroundColor DarkCyan
if (Test-CmdExists 'netlify') {
Write-Host ""
if (Get-YesNo "Log in to Netlify now?" $false) {
Write-Step "Opening Netlify login..."
netlify login
}
}
# ─── Cloudflare Wrangler (Pages + Workers) ──────────────────────────────────────
Write-Step "Checking Cloudflare Wrangler"
if (Test-CmdExists 'wrangler') {
Write-OK "Wrangler already installed"
Add-Result "Cloudflare Wrangler" "PRESENT" ""
}
else {
Write-Info "Wrangler deploys to Cloudflare Pages (static sites) and Workers (edge)."
if (Get-YesNo "Install Cloudflare Wrangler?") {
if (Test-CmdExists 'npm') {
try { npm install -g wrangler 2>&1 | Out-Null } catch { Write-Info "npm note: $_" }
Refresh-Path
if (Test-CmdExists 'wrangler') { Write-OK "Wrangler installed"; Add-Result "Cloudflare Wrangler" "INSTALLED" "" }
else { Write-Warn "Wrangler installed but needs terminal restart for PATH"; Add-Result "Cloudflare Wrangler" "INSTALLED" "Restart terminal" }
}
else { Write-Fail "npm not available - install Node.js first"; Add-Result "Cloudflare Wrangler" "FAILED" "npm missing" }
}
else { Add-Result "Cloudflare Wrangler" "SKIPPED" "" }
}
Write-Info "Cloudflare deploy quickstart:"
Write-Tip "wrangler login # browser OAuth"
Write-Tip "wrangler pages deploy .\dist --project-name=NAME # static site -> Pages"
Write-Tip "wrangler deploy # deploy a Worker (edge)"
Write-Tip "Headless/CI: set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID"
if (Test-CmdExists 'wrangler') {
if (Get-YesNo "Log in to Cloudflare now?" $false) { wrangler login }
}
# ─── Additional: Windows Terminal & Fonts ──────────────────────────────────────
Write-Step "Checking Windows Terminal"
$wtInstalled = Get-AppxPackage -Name "Microsoft.WindowsTerminal" -ErrorAction SilentlyContinue
if ($wtInstalled) {
Write-OK "Windows Terminal installed"
Add-Result "Windows Terminal" "PRESENT" ""
}
else {
Write-Info "Windows Terminal provides tabs, GPU rendering, and Nerd Font support."
if (Get-YesNo "Install Windows Terminal?") {
if (Test-CmdExists 'winget') {
winget install --id Microsoft.WindowsTerminal --accept-source-agreements --accept-package-agreements --silent 2>$null
Write-OK "Windows Terminal installed"
Add-Result "Windows Terminal" "INSTALLED" ""
}
else {
Write-Info "Install from Microsoft Store: search 'Windows Terminal'"
Add-Result "Windows Terminal" "SKIPPED" "Install from Store"
}
}
else {
Add-Result "Windows Terminal" "SKIPPED" ""
}
}
# UTF-8 console
Write-Step "Setting console to UTF-8"
$currentCP = chcp 2>$null
if ($currentCP -notmatch '65001') {
chcp 65001 > $null 2>&1
Write-OK "Console code page set to UTF-8 (65001)"
}
else {
Write-OK "Console already UTF-8"
}
# Vercel CLI
Write-Step "Checking Vercel CLI"
Refresh-Path
if (Test-CmdExists 'vercel') {
Write-OK "Vercel CLI already installed"
Add-Result "Vercel CLI" "PRESENT" ""
}
else {
Write-Info "Vercel is another popular hosting platform (like Netlify)."
if (Get-YesNo "Install Vercel CLI?" $false) {
if (Test-CmdExists 'npm') {
npm install -g vercel 2>&1 | Out-Null
Refresh-Path
Write-OK "Vercel CLI installed"
Add-Result "Vercel CLI" "INSTALLED" ""
}
}
else {
Add-Result "Vercel CLI" "SKIPPED" ""
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 4 - VALIDATION & HEALTH CHECK
# ═══════════════════════════════════════════════════════════════════════════════
Enter-Phase "Validation & Health Check"
Write-Section "PHASE 4: Environment Health Check"
Refresh-Path
$checks = @(
@{ Name = "PowerShell"; Cmd = "pwsh --version"; Alt = "`$PSVersionTable.PSVersion" },
@{ Name = "Git"; Cmd = "git --version"; Alt = $null },
@{ Name = "Git Bash"; Cmd = $null; Path = "$env:ProgramFiles\Git\bin\bash.exe" },
@{ Name = "Node.js"; Cmd = "node --version"; Alt = $null },
@{ Name = "npm"; Cmd = "npm --version"; Alt = $null },
@{ Name = "npx"; Cmd = "npx --version"; Alt = $null },
@{ Name = "Python"; Cmd = "python --version"; Alt = $null },
@{ Name = "pip"; Cmd = "pip --version"; Alt = $null },
@{ Name = "uv"; Cmd = "uv --version"; Alt = $null },
@{ Name = "Claude Code"; Cmd = "claude --version"; Alt = $null; Path2 = "$env:USERPROFILE\.local\bin\claude.exe" },
@{ Name = "GitHub CLI"; Cmd = "gh --version"; Alt = $null },
@{ Name = "Kimi Code CLI"; Cmd = "kimi --version"; Alt = $null },
@{ Name = "ripgrep"; Cmd = "rg --version"; Alt = $null },
@{ Name = "jq"; Cmd = "jq --version"; Alt = $null },
@{ Name = "ImageMagick"; Cmd = "magick --version"; Alt = $null },
@{ Name = "GhostScript"; Cmd = "gswin64c --version"; Alt = $null },
@{ Name = "FFmpeg"; Cmd = "ffmpeg -version"; Alt = $null },
@{ Name = "bat"; Cmd = "bat --version"; Alt = $null },
@{ Name = "fzf"; Cmd = "fzf --version"; Alt = $null },
@{ Name = "delta"; Cmd = "delta --version"; Alt = $null },
@{ Name = "shellcheck"; Cmd = "shellcheck --version"; Alt = $null },
@{ Name = "yq"; Cmd = "yq --version"; Alt = $null },
@{ Name = "Netlify CLI"; Cmd = "netlify --version"; Alt = $null },
@{ Name = "Vercel CLI"; Cmd = "vercel --version"; Alt = $null },
@{ Name = "Chocolatey"; Cmd = "choco --version"; Alt = $null }
)
# ─── Functional Validation (not just version checks) ──────────────────────────
Write-Step "Running functional validation tests"
$funcTests = @()
# Test Node.js can actually execute JavaScript
if (Test-CmdExists 'node') {
try {
$nodeTest = node -e "console.log('node-ok')" 2>&1
if ($nodeTest -match 'node-ok') { $funcTests += @{ Name = "Node.js exec"; Pass = $true } }
else { $funcTests += @{ Name = "Node.js exec"; Pass = $false } }
}
catch { $funcTests += @{ Name = "Node.js exec"; Pass = $false } }
}
# Test Python can import key AI packages
if (Test-CmdExists 'python') {
try {
$pyTest = python -c "import ssl; print('py-ssl-ok:', ssl.OPENSSL_VERSION)" 2>&1
if ($pyTest -match 'py-ssl-ok') { $funcTests += @{ Name = "Python SSL"; Pass = $true } }
else { $funcTests += @{ Name = "Python SSL"; Pass = $false } }
}
catch { $funcTests += @{ Name = "Python SSL"; Pass = $false } }
try {
$pyImport = python -c "import requests, pydantic; print('imports-ok')" 2>&1
if ($pyImport -match 'imports-ok') { $funcTests += @{ Name = "Python packages"; Pass = $true } }
else { $funcTests += @{ Name = "Python packages"; Pass = $false } }
}
catch { $funcTests += @{ Name = "Python packages"; Pass = $false } }
}
# Test npm registry connectivity
if (Test-CmdExists 'npm') {
try {
$npmPing = npm ping 2>&1
if ($LASTEXITCODE -eq 0) { $funcTests += @{ Name = "npm registry"; Pass = $true } }
else { $funcTests += @{ Name = "npm registry"; Pass = $false } }
}
catch { $funcTests += @{ Name = "npm registry"; Pass = $false } }
}
# Test Git can actually connect (credential check)
if (Test-CmdExists 'git') {
try {
$gitLs = git ls-remote --heads https://github.com/anthropics/claude-code.git 2>&1
if ($LASTEXITCODE -eq 0) { $funcTests += @{ Name = "Git HTTPS"; Pass = $true } }
else { $funcTests += @{ Name = "Git HTTPS"; Pass = $false } }
}
catch { $funcTests += @{ Name = "Git HTTPS"; Pass = $false } }
}
# Test ImageMagick delegates (GhostScript for PDF)
if (Test-CmdExists 'magick') {
try {
$delegates = magick identify -list format 2>&1
if ($delegates -match 'PDF') { $funcTests += @{ Name = "ImageMagick PDF"; Pass = $true } }
else { $funcTests += @{ Name = "ImageMagick PDF"; Pass = $false } }
}
catch { $funcTests += @{ Name = "ImageMagick PDF"; Pass = $false } }
}
foreach ($ft in $funcTests) {
if ($ft.Pass) {
Write-Host " $($ft.Name.PadRight(18)) " -NoNewline
Write-Host "PASS" -ForegroundColor Green
}
else {
Write-Host " $($ft.Name.PadRight(18)) " -NoNewline
Write-Host "FAIL" -ForegroundColor Red
}
}
Write-Host ""
$colName = "Component".PadRight(18)
$colStatus = "Status".PadRight(10)
$colVer = "Version / Details"
Write-Host " $colName $colStatus $colVer" -ForegroundColor White
Write-Host " $('-' * 18) $('-' * 10) $('-' * 35)" -ForegroundColor DarkGray
$passCount = 0
$failCount = 0
foreach ($check in $checks) {
$name = $check.Name.PadRight(18)
$ver = ""
$found = $false
# Check via command
if ($check.Cmd) {
try {
$ver = Invoke-Expression $check.Cmd 2>&1 | Select-Object -First 1
if ($LASTEXITCODE -eq 0 -or $ver) { $found = $true }
}
catch {}
}
# Check via path
if (-not $found -and $check.Path) {
if (Test-Path $check.Path) {
$found = $true
$ver = $check.Path
}
}
if (-not $found -and $check.Path2) {
if (Test-Path $check.Path2) {
$found = $true
$ver = $check.Path2
}
}
# Check via alt expression
if (-not $found -and $check.Alt) {
try {
$ver = Invoke-Expression $check.Alt 2>&1
if ($ver) { $found = $true }
}
catch {}
}
if ($found) {
$status = "PASS".PadRight(10)
$verStr = "$ver".Trim()
if ($verStr.Length -gt 35) { $verStr = $verStr.Substring(0, 35) }
Write-Host " $name " -NoNewline
Write-Host $status -NoNewline -ForegroundColor Green
Write-Host $verStr -ForegroundColor Gray
$passCount++
}
else {
$status = "MISSING".PadRight(10)
Write-Host " $name " -NoNewline
Write-Host $status -NoNewline -ForegroundColor Red
Write-Host "Not installed" -ForegroundColor DarkGray
$failCount++
}
}
Write-Host ""
Write-Host " $('-' * 65)" -ForegroundColor DarkGray
Write-Host " Total: $passCount passed, $failCount missing" -ForegroundColor $(if ($failCount -eq 0) { 'Green' } else { 'Yellow' })
# ═══════════════════════════════════════════════════════════════════════════════
# PHASE 5 - QUICK REFERENCE GUIDE
# ═══════════════════════════════════════════════════════════════════════════════
Complete-Progress "AI Terminal Kickstart"
Write-Section "PHASE 5: Quick Reference Guide"
$elapsed = (Get-Date) - $script:StartTime
Write-Host @"
┌─────────────────────────────────────────────────────────────────┐
│ HOW TO USE YOUR AI TERMINALS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CLAUDE CODE (Best for coding, files, agents) │
│ ───────────────────────────────────────────── │
│ claude Start interactive session │
│ claude "fix this bug" One-shot command │
│ claude --help See all options │
│ claude doctor Diagnose configuration │
│ claude mcp list Show MCP servers │
│ /help In-session help │
│ │
│ First-time auth: run 'claude' > browser opens > log in at │
│ claude.ai > click Authorize > return to terminal > done! │
│ │
│ Keyboard: Enter = send | Ctrl+C = cancel | Escape = exit │
│ Power moves: Ask it to edit files, run tests, create PRs │
│ │
│ CHATGPT TERMINAL (Best for quick Q&A, brainstorming) │
│ ───────────────────────────────────────────────── │
│ chatgpt Start interactive chat │
│ gpt Alias for chatgpt │
│ cgpt "explain X" Quick one-shot question │
│ /model gpt-4o Switch models mid-chat │
│ /save Export session to JSON │
│ /clear Reset conversation │
│ │
│ GITHUB COPILOT CLI (Best for shell commands) │
│ ───────────────────────────────────────────── │
│ gh copilot suggest "..." Get command suggestions │
│ gh copilot explain "..." Explain a command │
│ gh auth login Log in to GitHub first │
│ │
├─────────────────────────────────────────────────────────────────┤
│ NETLIFY DEPLOYMENT (direct deploy from any AI terminal) │
│ ────────────────────────────────────────────────── │
│ netlify login Authenticate (one-time) │
│ cd C:\path\to\site Go to your site folder │
│ netlify init Link to Netlify site (one-time) │
│ netlify deploy Preview deploy (draft URL) │
│ netlify deploy --prod Push to production (live URL) │
│ netlify open Open live site in browser │
│ netlify env:set KEY val Set environment variables │
│ │
│ AI + DEPLOY WORKFLOWS: │
│ Claude: cd site && claude │
│ > fix the footer and deploy a draft to Netlify │
│ ChatGPT: gpt > ask for code > paste into file > netlify deploy│
│ Copilot: gh copilot suggest "deploy to Netlify" │
│ │
├─────────────────────────────────────────────────────────────────┤
│ GUI & WEB INTERFACES │
│ ──────────────────── │
│ Claude: https://claude.ai/code (web app) │
│ VS Code extension: "Claude Code" in marketplace │
│ JetBrains extension: search "Claude" in plugins │
│ ChatGPT: https://chatgpt.com (web interface) │
│ Desktop app: https://openai.com/chatgpt/download │
│ Copilot: Built into VS Code (GitHub Copilot extension) │
│ Built into github.com (Copilot Chat in repos) │
│ Netlify: https://app.netlify.com (dashboard) │
│ │
├─────────────────────────────────────────────────────────────────┤
│ GOOGLE DRIVE & APP CONNECTORS (via MCP Servers) │
│ ─────────────────────────────────────────────── │
│ Claude Code can connect to external apps via MCP: │
│ │
│ Google Drive: │
│ claude mcp add gdrive -- npx @anthropic/gdrive-mcp │
│ (requires Google OAuth - follow the prompts) │
│ Then: > list my Google Drive files │
│ > read the doc called "Project Plan" │
│ │
│ Slack: │
│ claude mcp add slack -- npx @anthropic/slack-mcp │
│ Then: > check #general for recent messages │
│ │
│ GitHub: │
│ claude mcp add github -- npx @anthropic/github-mcp │
│ Then: > list open PRs in my repo │
│ │
│ Filesystem (already built-in): │
│ Claude Code reads/writes files in your current directory │
│ by default - no extra setup needed. │
│ │
│ Playwright (browser automation): │
│ claude mcp add playwright -- npx @playwright/mcp@latest │
│ Then: > take a screenshot of my site at localhost:8080 │
│ │
│ COMMON TIPS FOR ALL TERMINALS │
│ ───────────────────────────── │
│ - Be specific in your prompts for better results │
│ - Use quotes around multi-word arguments │
│ - Start simple, then add detail if output is wrong │
│ - Check 'claude doctor' or '/help' if something breaks │
│ - Restart terminal after fresh installs for PATH updates │
│ │
└─────────────────────────────────────────────────────────────────┘
"@ -ForegroundColor Cyan
# ═══════════════════════════════════════════════════════════════════════════════
# FINAL SUMMARY
# ═══════════════════════════════════════════════════════════════════════════════
Write-Host ""
Write-Host " ╔═══════════════════════════════════════════════════════════════════╗" -ForegroundColor Green
Write-Host " ║ KICKSTART COMPLETE ║" -ForegroundColor Green
Write-Host " ╠═══════════════════════════════════════════════════════════════════╣" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
# Display results table
$colComp = "Component".PadRight(22)
$colStat = "Status".PadRight(12)
$colDetail = "Detail"
Write-Host " ║ $colComp $colStat $colDetail" -ForegroundColor Green
Write-Host " ║ $('-' * 22) $('-' * 12) $('-' * 25)" -ForegroundColor DarkGreen
foreach ($r in $script:Results) {
$comp = $r.Component.PadRight(22)
$stat = $r.Status.PadRight(12)
$det = if ($r.Detail.Length -gt 25) { $r.Detail.Substring(0, 25) } else { $r.Detail }
$color = switch -Regex ($r.Status) {
'INSTALL|PRESENT|RAN|OK' { 'Green' }
'SKIPPED|PARTIAL' { 'Yellow' }
'FAILED|ERROR' { 'Red' }
default { 'Gray' }
}
Write-Host " ║ " -NoNewline -ForegroundColor Green
Write-Host "$comp " -NoNewline -ForegroundColor $color
Write-Host "$stat " -NoNewline -ForegroundColor $color
Write-Host "$det" -ForegroundColor Gray
}
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ║ Time elapsed: $($elapsed.ToString('mm\:ss')) ║" -ForegroundColor Green
Write-Host " ║ Log file : $($script:LogFile.PadRight(45)) ║" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ║ NEXT STEPS: ║" -ForegroundColor Green
Write-Host " ║ 1. CLOSE this terminal and open a NEW one (PATH refresh) ║" -ForegroundColor Green
Write-Host " ║ 2. Run 'claude' to start Claude Code ║" -ForegroundColor Green
Write-Host " ║ 3. Run 'chatgpt' or 'gpt' for ChatGPT ║" -ForegroundColor Green
Write-Host " ║ 4. Run 'gh auth login' then 'gh copilot suggest ...' ║" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ╚═══════════════════════════════════════════════════════════════════╝" -ForegroundColor Green
Write-Host ""
# Cleanup temp if empty
if ((Get-ChildItem $script:TempDir -File -ErrorAction SilentlyContinue).Count -le 1) {
# Keep the log file, remove installers
Get-ChildItem $script:TempDir -File -Exclude "kickstart.log" -ErrorAction SilentlyContinue |
Remove-Item -Force -ErrorAction SilentlyContinue
}
Write-Host " Press any key to exit..." -ForegroundColor Gray
try { $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } catch {}
```powershell
# One-time: allow the current session to run scripts
Set-ExecutionPolicy Bypass -Scope Process -Force
# Install Chocolatey (Windows package manager)
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
# Install runtimes + utilities
choco install -y git nodejs-lts python312 ripgrep jq fd 7zip imagemagick pwsh
# Verify
node --version ; npm --version ; python --version ; git --version
Linux (Bash, Ubuntu / Debian / Mint)
# Update + install runtimes + utilities
sudo apt update
sudo apt install -y git curl build-essential python3 python3-pip python3-venv ripgrep jq fd-find imagemagick
# Install Node.js LTS via NodeSource (the apt package is usually out of date)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version ; npm --version ; python3 --version ; git --version
Linux (Bash, Fedora / RHEL / Rocky)
sudo dnf install -y git curl python3 python3-pip ripgrep jq fd-find ImageMagick
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs
Close the terminal and open a new one after runtime installs. PATH updates only take effect in fresh sessions.
Step 2 — Install Claude Code
Anthropic's AI coding terminal. Reads your repo, edits files, runs commands, opens PRs.
Windows (PowerShell)
# Official installer (puts 'claude' on your PATH)
irm https://claude.ai/install.ps1 | iex
Linux (Bash)
curl -fsSL https://claude.ai/install.sh | sh
Both platforms verify:
claude --version
Authenticate once by just running claude in any directory, it opens a browser to claude.ai, you click Authorize, and you're done. No API key needed for the browser flow. If you prefer an API key (required for CI):
# Windows (persists across sessions):
[Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-your-key-here", "User")
# Linux:
echo 'export ANTHROPIC_API_KEY="sk-ant-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Get an API key at console.anthropic.com/settings/keys.
Step 3 — Install OpenAI Codex / ChatGPT CLI
The official OpenAI CLI for coding (Codex) and a quick ChatGPT terminal for one-shot questions.
Both platforms
# Official OpenAI Codex CLI (Node-based)
npm install -g @openai/codex
# Verify
codex --version
For a lightweight ChatGPT streaming terminal (python-based alternative):
pip install chatgpt-cli
# One-shot
chatgpt "explain rsync incremental backups in one paragraph"
# Interactive
chatgpt
Authenticate by setting your OpenAI key. Get one at platform.openai.com/api-keys (requires a paid account, $5 prepaid is enough).
# Windows
[Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-your-key-here", "User")
# Linux
echo 'export OPENAI_API_KEY="sk-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Step 4 — Install GitHub Copilot CLI
Copilot CLI lives inside the gh (GitHub) CLI as an extension.
Windows (PowerShell)
choco install -y gh
gh auth login # web browser flow — select GitHub.com + browser
gh extension install github/gh-copilot
Linux (Bash, Ubuntu / Debian / Mint)
# Add GitHub CLI apt repo
type -p curl >/dev/null || sudo apt install -y curl
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
gh auth login # choose GitHub.com → web browser → paste one-time code
gh extension install github/gh-copilot
Linux (Bash, Fedora / RHEL)
sudo dnf install -y 'dnf-command(config-manager)'
sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
sudo dnf install -y gh
gh auth login
gh extension install github/gh-copilot
Verify:
gh copilot suggest "find all PNG files larger than 1MB in current directory"
gh copilot explain "tar -xzf archive.tar.gz -C /opt/"
Copilot CLI requires a Copilot subscription ($10/month individual, free for students / verified OSS maintainers).
Step 5 — Install Netlify CLI
For deploying sites directly from the terminal after Claude / Codex writes them.
Both platforms
npm install -g netlify-cli
netlify --version
netlify login # browser opens → Authorize → back to terminal
Verify auth:
netlify status
Step 6 — Install Python AI SDKs (optional but useful)
For scripting against the APIs directly (no terminal UI).
# Both platforms — use pip (Linux may need python3 -m pip)
pip install anthropic openai google-generativeai fastmcp mcp requests httpx pandas
Step 7 — Your first session
From your project directory:
cd ~/my-website # or: cd $env:USERPROFILE\my-website on Windows
claude
Then at the Claude prompt:
> give me a summary of this project
> add a dark-mode toggle to the header, commit, and show me the diff
> deploy a draft to Netlify so I can preview it
Claude reads your files, edits them, runs commands, and reports back. When the draft looks right:
> deploy to production
One-shot mode (any of the three CLIs)
For quick answers without entering a session:
claude "explain what package.json does in this repo"
codex "write a bash script that rotates log files older than 14 days"
chatgpt "what's the difference between sync and async in Node.js?"
gh copilot suggest "rebase my feature branch on main without merge commits"
Common gotchas
command not foundafter install. Close and reopen your terminal. PATH changes only take effect in new sessions.Execution policyerror on Windows.Set-ExecutionPolicy Bypass -Scope Process -Forcethen re-run.Permission deniedon Linux scripts.chmod +x ./script.shthen rerun.- Netlify deploy shows wrong files. Specify the directory explicitly:
netlify deploy --dir=_site --prod. claudesays API key missing. Either runclaudewith no args (triggers browser OAuth) orecho $ANTHROPIC_API_KEYto confirm it's set.- OpenAI
insufficient_quotaerror. The API tier is separate from ChatGPT Plus, prepay $5 at platform.openai.com/account/billing. - Copilot CLI says "no subscription." A GitHub Pro / Team / Enterprise account doesn't include Copilot, you need the $10/mo Copilot plan or be in the free student/OSS program.
Cheat sheet — the commands you'll use daily
# Start AI sessions
claude # Claude Code interactive
claude "one-shot prompt" # Quick Claude query
codex # OpenAI Codex interactive
chatgpt # ChatGPT streaming terminal
gh copilot suggest "…" # Shell command suggestion
# Inside Claude Code
/help # slash commands
/clear # reset conversation
/mcp list # show connected services
Esc # exit
# Git (used constantly)
git status ; git diff ; git add . ; git commit -m "msg"
# Deploy
netlify deploy # preview URL
netlify deploy --prod # live
netlify deploy --dir=_site --prod # for SSG builds (Eleventy etc.)
Optional — connect Claude to Google Drive, Slack, GitHub via MCP
MCP (Model Context Protocol) servers let Claude read external apps from inside the terminal.
# Google Drive
claude mcp add gdrive -- npx @anthropic/gdrive-mcp
# Slack
claude mcp add slack -- npx @anthropic/slack-mcp
# GitHub (enhanced — reads issues, PRs, etc.)
claude mcp add github -- npx @anthropic/github-mcp
# Playwright (browser automation — screenshots, form-filling)
claude mcp add playwright -- npx @playwright/mcp@latest
# List / remove
claude mcp list
claude mcp remove gdrive
First run of each MCP prompts for OAuth. The tokens live locally; nothing is sent to Anthropic or OpenAI outside the normal API calls.
Why the terminal wins over the web UI
The 30-second argument: your code lives on the filesystem; your git history lives in the terminal; your builds run in the terminal; your deploys happen from the terminal. The web GUI makes you leave where the work happens, describe your problem in isolation, receive a response in isolation, then manually carry that response back to where the work happens. The terminal eliminates the round trip entirely.
On a 50-file refactor, the terminal approach takes 2 minutes. The GUI approach takes 45 minutes of copy-paste-switch-paste-switch-paste.
Side-by-side on one concrete task
Task: "Add a dark mode toggle to my website."
Web GUI (ChatGPT / Claude.ai):
- Open browser → claude.ai (8s)
- "add dark mode toggle" → AI asks for HTML → open file explorer, find
index.html, copy → paste in browser - AI asks for CSS → repeat for
styles.css - AI returns three code blocks → copy HTML → switch to editor → find insertion point → paste
- Copy CSS → find insertion point → paste
- Copy JS → create
toggle.js→ paste → add<script>tag to HTML - Refresh browser, hit a bug, copy the error, paste back, get fix, copy, switch, paste, test again
~20 steps, ~15 window switches, ~130 seconds of pure mechanical copy-paste.
Terminal (Claude Code):
cd ~/my-website && claude> add a dark mode toggle to the site- Press
yto approve the diff.
3 steps, 0 window switches, ~7 seconds of input. Time saved: ~2 minutes per change. Over a workday of 30 changes, that's an hour recovered.
What the terminal gives the model that the GUI can't
- Real filesystem access, every
.md,.njk,.toml,.envin your working directory, read on demand. No more copy-pasting individual files and forgetting one. - Command execution —
git status,npm run build,netlify deploy --prod,curlhealth checks, run and reacted to in the same loop, not copied out and run by you. - Multi-file atomic edits, rename a variable across 40 files, update imports, run tests, revert if anything breaks, one session. Browser UI is one file at a time and loses context each round-trip.
- Full git integration, commits with meaningful messages, branches, PR open, diff review, all native.
- No token bloat from pasted context, CLI streams file contents only when the model asks. Browser burns context on every copy-paste.
Speed, memory, battery
| Metric | Terminal | Web GUI |
|---|---|---|
| Startup | 1–2 s | 5–15 s |
| RAM | 50–150 MB | 300–800 MB per tab |
| Input latency | Instant | 50–100 ms (JS overhead) |
| File read | <1 ms (local) | Manual upload (2–30 s) |
| File write | <1 ms (local) | Copy-paste (10–30 s) |
| Battery hit (1 hr laptop) | ~2 % | ~8–15 % |
Over an 8-hour day of heavy AI use: terminal ~400 MB RAM, minimal battery hit. Browser ~3–5 GB RAM across tabs, significant battery drain.
Multi-file tasks the GUI cannot do
| Task | Terminal | Web GUI |
|---|---|---|
| Rename a function across 30 files | 15 s | 45 min |
| Add a header to 50 HTML pages | 20 s | 2 hrs |
| Update import paths across 100 files after a folder rename | 10 s | impossible |
Find & fix every console.log before deploy |
5 s | 30 min |
| Add error handling to every API call in the project | 30 s | 1 hr |
| Update copyright year in every page footer | 3 s | 20 min |
Web GUI fundamentally has no filesystem access, you'd paste files one at a time, get changes one at a time, apply them one at a time.
Security and audit
| Risk | Terminal | Web GUI |
|---|---|---|
| Browser extensions reading your code / API keys | Not possible | Major, extensions can read all page content |
| Clipboard exposure | Minimal (direct file writes) | Every copy-paste puts code on the clipboard |
| Session hijacking | API key in env var | Session cookie in browser (XSS surface) |
| Data in browser cache | None | Conversations cached in localStorage |
| Audit trail | Shell history (local file) | Proprietary, deletable |
API keys in the terminal are stored in environment variables, never displayed, OS-file-permission protected, invisible to browser extensions, and not sent to analytics or tracking services.
Where the terminal is the only option
- Remote servers & SSH, you can't open claude.ai on a headless server.
ssh prod && claudeis the whole workflow. Read nginx logs, fix config, restart service, one prompt. - Persistent sessions —
tmux new -s ai-session+claude, detach, close SSH, go home. Come back next morning,tmux attach -t ai-session, Claude is still there reporting what it found. - CI/CD —
claude --print "run the release checklist"in a GitHub Action, Vercel hook, or cron job. The browser cannot be scripted. - MCP connectors, Google Drive, Slack, GitHub, Playwright, databases. All terminal-only. The web GUI has zero MCP support.
When the GUI actually wins
- You want an image or voice conversation.
- You're on a Chromebook / iPad where you can't install tools.
- You want to share a conversation link.
- You're drag-dropping a PDF for one-off analysis.
- You're walking or driving and want voice interaction (mobile app).
That's ~10–20 % of professional AI coding work. The terminal handles the other 80–90 % faster, safer, and more reliably.
How Claude Code CLI + Netlify CLI work together for easy administration
Once both are installed and authenticated, the whole admin surface of a website collapses into prompts. Here's the pattern for the most common ops:
Deploy a change end-to-end
cd ~/my-website
claude
> add a privacy policy page that covers cookies and analytics, link it
from the footer, run the build, and deploy a draft to Netlify
Claude writes the page, updates the footer, runs npm run build (or npx @11ty/eleventy), runs netlify deploy, returns the preview URL. You check the preview in a browser, then:
> looks good, deploy to production and ping IndexNow
Add a redirect without touching config files
> add a 301 redirect from /old-page to /new-page in netlify.toml
and verify it works after deploy with curl
Claude edits netlify.toml, deploys, runs curl -I https://site.com/old-page to check for the Location: header. One prompt, proof-of-fix included.
Set environment variables securely
> set a Netlify environment variable SENDGRID_API_KEY to the value
I'll paste next, then redeploy
Claude uses netlify env:set SENDGRID_API_KEY ... (the value stays in the terminal session, never written to a file). Then it triggers a redeploy so the new env is picked up.
Investigate a production issue
> pull the last 3 Netlify deploy logs, summarize any errors, and
suggest a fix for each
netlify api listSiteDeploys returns the deploy history; Claude reads the logs, spots the pattern (build failure, timeout, bad env), and proposes the patch.
Migrate or clone a site
> this folder is a working Eleventy site. Create a new Netlify site
called "alpha-staging", link this folder to it, and do a prod deploy
Claude runs netlify sites:create --name alpha-staging, then netlify link --name alpha-staging, then netlify deploy --dir=_site --prod. Zero clicks in the Netlify dashboard.
Rollback fast
> the last deploy broke the homepage, restore the previous production
deploy
Claude uses netlify api listSiteDeploys to find the previous successful deploy ID, then netlify api restoreSiteDeploy to roll back. ~5 seconds.
The through-line: every Netlify admin action has a CLI equivalent, and every CLI equivalent is something Claude Code can run from a prompt. Once you accept that, the Netlify dashboard becomes a read-only status page and everything mutating happens via prompt.
Cross-promote: pair the terminal with the prompt generators on this site
The terminal gives Claude real filesystem + command access. The jwatte.com tool suite gives Claude the exact thing to do. That pairing is the whole productivity story.
Write better prompts before you hand them to Claude:
- Prompt Enhancer, wraps any prompt in research-backed patterns (ExpertPrompting, OPRO, EmotionPrompt, self-evaluation). Paste your rough instruction, pick an intensity, copy the enhanced version, hand it to
claudeorcodex. Companion post →
Generate ready-to-paste prompts from audit data:
- Mega Analyzer, audits one URL across SEO, schema, E-E-A-T, voice, mobile, performance, AI-search. The output ends in a multi-thousand-word AI fix prompt that names exact file paths and expected score deltas. Pipe that into
claudeand you go from "here's what's broken" to "here's the fix, committed and deployed" in one session. - Site Analyzer, same pattern, 70+ checks, different scoring buckets. Also emits a copy-paste AI fix prompt.
- Batch Compare, up to 10 URLs compared side-by-side with a combined AI prompt covering the whole portfolio. Feed it to Claude and fix 10 sites from one session.
- Link Graph, crawls a site, finds orphans / hubs / dead-ends / noindex pages, and emits an AI fix prompt that proposes exact internal-link additions (source page + destination + anchor text). Paste, tell Claude "apply every recommendation, commit grouped by destination page, deploy", done.
Scaffold new sites from a prompt:
- Single Site Gen, emits a full AI site-build prompt with every best practice (schema, llms.txt, IndexNow, security headers, E-E-A-T signals, WCAG 2.2) baked in.
claude < singlesitegen-prompt.txtand Claude scaffolds the whole thing in~/new-site/. - Monoclone Generator, for when you're spinning up an industry site from a template. Generates the deploy prompt for the whole network.
Generate JSON-LD / schema blocks Claude can drop in directly:
- E-E-A-T Generator, Person / Organization / sameAs / Wikidata / ORCID / rel=me JSON-LD from a single author profile.
- Speakable Generator, SpeakableSpecification JSON-LD for voice + AI-citation.
- FAQ Harvester, pulls every FAQ from the Google top 10, dedupes, emits ready-to-paste FAQPage JSON-LD.
- ItemList / Carousel, Google-compliant ItemList JSON-LD from a URL list.
Audit without leaving the terminal:
- .well-known Audit, 13-file audit of
/.well-known/with copy-paste fix kit. - ai.txt Generator, Spawning-style AI training opt-in/out policy across every bot in the shared registry (robots.txt companion, Netlify/Apache deploy config).
Concrete workflow that ties all of it together:
# 1. Browser: run /tools/mega-analyzer/ on your site, click "Copy AI fix prompt"
# 2. Terminal:
cd ~/my-site
claude
> [paste the Mega Analyzer fix prompt]
# Claude works through every check, edits the files, commits
> run a mobile parity check, then deploy to Netlify prod if pass
# Claude runs /tools/mobile-parity/ mentally, or you paste its URL as input
The tools produce prompts; the terminal consumes prompts. The browser is just the scratchpad where you collect the prompt.
When to reach for which tool
- Editing a real project, multi-file changes, deploys. → Claude Code. It's the only one that reads + writes files end-to-end in your working directory.
- Quick one-off code snippet or explanation. → Codex or
chatgptone-shot mode. Faster, cheaper, no project context needed. - "What's the shell command for X?" → Copilot CLI. Specifically trained for the one-line shell suggestion task.
- GUI preference / on a tablet or Chromebook. → claude.ai/code (browser-based Claude Code), chatgpt.com, VS Code Copilot extension.
The terminal versions are faster for anything that involves your local repo. The GUI versions are better for pure conversation or when you can't install locally.
Related reading
Next up — the follow-on walkthrough: You've Got The Claude Code CLI Installed — Now What? picks up where this post leaves off. Plan mode, CLAUDE.md as a living contract, the slash commands that matter (/btw /fork /rewind /compact /loop /schedule /simplify /batch /powerup /insights /debug /claude-api), Skills vs Rules vs Memory, Auto Mode boundaries, and a Day 0 → Day 7 ramp plan.
Companion coverage from the methodology stack:
- The $97 Launch, Chapter 1 (domain + hosting + deploy). Claude Code + Netlify CLI is the end-state of the workflow that chapter sets up.
- The $20 Dollar Agency, Chapters 5-11 (SEO, schema, keywords). Every audit pattern the Site Analyzer and Mega Analyzer run is something Claude Code can implement in one prompt once the environment is live.
- The $100 Network, Chapter 6 (the provider stack) + Chapter 26 (monitoring at scale). Where the AI terminal fits when you're running more than one site. ) { $idx = [int]$input - 1 if ($idx -ge 0 -and $idx -lt $Options.Count) { return $idx } } return $Default }
function Get-YesNo { param([string]$Prompt, [bool]$Default = $true) if ($AutoMode) { return $Default } $hint = if ($Default) { "Y/n" } else { "y/N" } Write-Host " $Prompt ($hint): " -NoNewline -ForegroundColor Yellow $answer = Read-Host if ([string]::IsNullOrWhiteSpace($answer)) { return $Default } return ($answer -match '^[Yy]') }
function Invoke-SubScript { param( [string]$Name, [string]$FileName, [hashtable]$Params = @{} ) $path = Join-Path $ScriptsDir $FileName if (-not (Test-Path $path)) { Write-Warn "Script not found: $path" Write-Info "Skipping $Name - file missing from scripts directory" Add-Result $Name "SKIPPED" "Script file not found" return $false } Write-Info "Running: $FileName" try { & $path @Params Add-Result $Name "RAN" "Sub-script executed" return $true } catch { Write-Fail "$Name script error: $" Add-Result $Name "ERROR" "$" return $false } }
═══════════════════════════════════════════════════════════════════════════════
PHASE 0 - INITIALIZATION
═══════════════════════════════════════════════════════════════════════════════
if (-not (Test-Path $script:TempDir)) { New-Item -Path $script:TempDir -ItemType Directory -Force | Out-Null }
Write-Banner
Show system info
$osInfo = Get-CimInstance Win32_OperatingSystem $psVer = $PSVersionTable.PSVersion $isAdmin = Test-IsAdmin
Write-Host " System Info:" -ForegroundColor White Write-Host " OS: $($osInfo.Caption)" -ForegroundColor Gray Write-Host " Build: $($osInfo.BuildNumber)" -ForegroundColor Gray Write-Host " PowerShell: $psVer" -ForegroundColor Gray Write-Host " Admin: $isAdmin" -ForegroundColor $(if ($isAdmin) { 'Green' } else { 'Yellow' }) Write-Host " Scripts Dir: $ScriptsDir" -ForegroundColor Gray Write-Host " Log File: $($script:LogFile)" -ForegroundColor Gray Write-Host ""
if (-not $isAdmin) { Write-Host " ┌──────────────────────────────────────────────────────────────┐" -ForegroundColor Yellow Write-Host " │ WARNING: Not running as Administrator. │" -ForegroundColor Yellow Write-Host " │ Some installations may fail or prompt UAC. │" -ForegroundColor Yellow Write-Host " │ Right-click PowerShell > Run as Administrator for best │" -ForegroundColor Yellow Write-Host " │ results. │" -ForegroundColor Yellow Write-Host " └──────────────────────────────────────────────────────────────┘" -ForegroundColor Yellow Write-Host "" }
─── Pre-flight: TLS, Network, Disk, Admin, Repos ─────────────────────────────
Enter-Phase "Pre-flight Checks" Write-Section "PRE-FLIGHT CHECKS"
Force TLS 1.2+ for all web requests in this session
Write-Step "Enforcing TLS 1.2+ for all downloads" [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13 Write-OK "TLS 1.2/1.3 enforced for all Invoke-WebRequest/RestMethod calls" Add-Result "TLS Security" "OK" "TLS 1.2+"
Admin access validation
Write-Step "Checking administrator privileges" if ($isAdmin) { Write-OK "Running as Administrator - full install capability" Add-Result "Admin Access" "OK" "Elevated" } else { Write-Warn "NOT running as Administrator" Write-Info "The following will fail without admin: Chocolatey, execution policy," Write-Info "system PATH changes, some MSI installers." Write-Info "Recommendation: Close and re-run as Administrator." Add-Result "Admin Access" "WARN" "Not elevated" }
Network connectivity - test multiple download sources
Write-Step "Testing internet connectivity to download sources" $script:NetworkOK = $true $endpoints = @( @{ Name = "GitHub API"; URL = "https://api.github.com" }, @{ Name = "Node.js"; URL = "https://nodejs.org/dist/index.json" }, @{ Name = "Claude installer"; URL = "https://claude.ai" }, @{ Name = "Chocolatey"; URL = "https://community.chocolatey.org" }, @{ Name = "npm registry"; URL = "https://registry.npmjs.org" }, @{ Name = "PyPI"; URL = "https://pypi.org" } ) $reachable = 0 $unreachable = 0 foreach ($ep in $endpoints) { try { $null = Invoke-WebRequest -Uri $ep.URL -UseBasicParsing -TimeoutSec 8 -Method Head -ErrorAction Stop Write-OK "$($ep.Name) reachable" $reachable++ } catch { Write-Warn "$($ep.Name) ($($ep.URL)) - UNREACHABLE" $unreachable++ } } if ($unreachable -gt 0) { Write-Warn "$unreachable of $($endpoints.Count) download sources unreachable." Write-Info "Check your network, proxy, or firewall settings." if ($unreachable -ge 4) { Write-Fail "Most download sources are unreachable. Script will likely fail." $script:NetworkOK = $false } } else { Write-OK "All $reachable download sources reachable" } Add-Result "Network" $(if ($unreachable -eq 0) { "OK" } elseif ($unreachable -lt 4) { "PARTIAL" } else { "FAILED" }) "$reachable/$($endpoints.Count) reachable"
Disk space check
Write-Step "Checking available disk space" try { $sysDrive = (Get-Item $env:SystemRoot).PSDrive $freeGB = [math]::Round(($sysDrive.Free / 1GB), 1) $totalGB = [math]::Round(($sysDrive.Used + $sysDrive.Free) / 1GB, 1) if ($freeGB -lt 2) { Write-Fail "CRITICAL: Only $freeGB GB free on $($sysDrive.Name): ($totalGB GB total). Need at least 2 GB." Write-Info "Free up disk space before continuing." Add-Result "Disk Space" "FAILED" "$freeGB GB free" } elseif ($freeGB -lt 5) { Write-Warn "Low: $freeGB GB free on $($sysDrive.Name): ($totalGB GB total). Recommend 5+ GB." Add-Result "Disk Space" "WARN" "$freeGB GB free" } else { Write-OK "Disk space: $freeGB GB free on $($sysDrive.Name): ($totalGB GB total)" Add-Result "Disk Space" "OK" "$freeGB GB free" } } catch {
Fallback for non-standard PSDrive
Write-Warn "Could not determine disk space: $_" Add-Result "Disk Space" "UNKNOWN" "" }
Log session separator
Add-Content -Path $script:LogFile -Value "`n$('=' * 70)" -ErrorAction SilentlyContinue Add-Content -Path $script:LogFile -Value "Session started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ErrorAction SilentlyContinue Add-Content -Path $script:LogFile -Value "OS: $($osInfo.Caption) Build $($osInfo.BuildNumber) | PS: $psVer | Admin: $isAdmin" -ErrorAction SilentlyContinue Add-Content -Path $script:LogFile -Value "$('=' * 70)" -ErrorAction SilentlyContinue
═══════════════════════════════════════════════════════════════════════════════
PHASE 1 - POWERSHELL 7 CHECK / UPGRADE
═══════════════════════════════════════════════════════════════════════════════
Enter-Phase "PowerShell & Core Prerequisites" Write-Section "PHASE 1: PowerShell Version Check"
if ($psVer.Major -ge 7) { Write-OK "Already running PowerShell $psVer - no upgrade needed" Add-Result "PowerShell 7" "PRESENT" "v$psVer" } elseif ($SkipPowerShell7) { Write-Warn "PowerShell 7 upgrade skipped (--SkipPowerShell7 flag)" Add-Result "PowerShell 7" "SKIPPED" "User opted out" } else { Write-Step "You are on PowerShell $psVer (Windows built-in)" Write-Info "PowerShell 7 is recommended for AI terminals. It offers:" Write-Info " - Better performance and modern language features" Write-Info " - Native JSON/REST support improvements" Write-Info " - Cross-platform compatibility" Write-Info " - Required by some advanced Claude Code features" Write-Host ""
if (Get-YesNo "Install PowerShell 7?") { Write-Step "Installing PowerShell 7..."
$installed = $false
Try winget first
if (Test-CmdExists 'winget') { Write-Info "Using winget to install PowerShell 7..." try { $result = winget install --id Microsoft.PowerShell --accept-source-agreements --accept-package-agreements --silent 2>&1 if ($LASTEXITCODE -eq 0 -or ($result -match 'already installed')) { $installed = $true Write-OK "PowerShell 7 installed via winget" } } catch { Write-Info "winget method failed, trying MSI..." } }
Fallback to MSI download
if (-not $installed) { Write-Info "Downloading PowerShell 7 MSI from GitHub..." try { $releases = Invoke-RestMethod 'https://api.github.com/repos/PowerShell/PowerShell/releases/latest' $msiAsset = $releases.assets | Where-Object { $_.name -match 'win-x64.msi
Windows (PowerShell, as Administrator)
# One-time: allow the current session to run scripts
Set-ExecutionPolicy Bypass -Scope Process -Force
# Install Chocolatey (Windows package manager)
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
# Install runtimes + utilities
choco install -y git nodejs-lts python312 ripgrep jq fd 7zip imagemagick pwsh
# Verify
node --version ; npm --version ; python --version ; git --version
Linux (Bash, Ubuntu / Debian / Mint)
# Update + install runtimes + utilities
sudo apt update
sudo apt install -y git curl build-essential python3 python3-pip python3-venv ripgrep jq fd-find imagemagick
# Install Node.js LTS via NodeSource (the apt package is usually out of date)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version ; npm --version ; python3 --version ; git --version
Linux (Bash, Fedora / RHEL / Rocky)
sudo dnf install -y git curl python3 python3-pip ripgrep jq fd-find ImageMagick
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs
Close the terminal and open a new one after runtime installs. PATH updates only take effect in fresh sessions.
Step 2 — Install Claude Code
Anthropic's AI coding terminal. Reads your repo, edits files, runs commands, opens PRs.
Windows (PowerShell)
# Official installer (puts 'claude' on your PATH)
irm https://claude.ai/install.ps1 | iex
Linux (Bash)
curl -fsSL https://claude.ai/install.sh | sh
Both platforms verify:
claude --version
Authenticate once by just running claude in any directory, it opens a browser to claude.ai, you click Authorize, and you're done. No API key needed for the browser flow. If you prefer an API key (required for CI):
# Windows (persists across sessions):
[Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-your-key-here", "User")
# Linux:
echo 'export ANTHROPIC_API_KEY="sk-ant-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Get an API key at console.anthropic.com/settings/keys.
Step 3 — Install OpenAI Codex / ChatGPT CLI
The official OpenAI CLI for coding (Codex) and a quick ChatGPT terminal for one-shot questions.
Both platforms
# Official OpenAI Codex CLI (Node-based)
npm install -g @openai/codex
# Verify
codex --version
For a lightweight ChatGPT streaming terminal (python-based alternative):
pip install chatgpt-cli
# One-shot
chatgpt "explain rsync incremental backups in one paragraph"
# Interactive
chatgpt
Authenticate by setting your OpenAI key. Get one at platform.openai.com/api-keys (requires a paid account, $5 prepaid is enough).
# Windows
[Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-your-key-here", "User")
# Linux
echo 'export OPENAI_API_KEY="sk-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Step 4 — Install GitHub Copilot CLI
Copilot CLI lives inside the gh (GitHub) CLI as an extension.
Windows (PowerShell)
choco install -y gh
gh auth login # web browser flow — select GitHub.com + browser
gh extension install github/gh-copilot
Linux (Bash, Ubuntu / Debian / Mint)
# Add GitHub CLI apt repo
type -p curl >/dev/null || sudo apt install -y curl
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
gh auth login # choose GitHub.com → web browser → paste one-time code
gh extension install github/gh-copilot
Linux (Bash, Fedora / RHEL)
sudo dnf install -y 'dnf-command(config-manager)'
sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
sudo dnf install -y gh
gh auth login
gh extension install github/gh-copilot
Verify:
gh copilot suggest "find all PNG files larger than 1MB in current directory"
gh copilot explain "tar -xzf archive.tar.gz -C /opt/"
Copilot CLI requires a Copilot subscription ($10/month individual, free for students / verified OSS maintainers).
Step 5 — Install Netlify CLI
For deploying sites directly from the terminal after Claude / Codex writes them.
Both platforms
npm install -g netlify-cli
netlify --version
netlify login # browser opens → Authorize → back to terminal
Verify auth:
netlify status
Step 6 — Install Python AI SDKs (optional but useful)
For scripting against the APIs directly (no terminal UI).
# Both platforms — use pip (Linux may need python3 -m pip)
pip install anthropic openai google-generativeai fastmcp mcp requests httpx pandas
Step 7 — Your first session
From your project directory:
cd ~/my-website # or: cd $env:USERPROFILE\my-website on Windows
claude
Then at the Claude prompt:
> give me a summary of this project
> add a dark-mode toggle to the header, commit, and show me the diff
> deploy a draft to Netlify so I can preview it
Claude reads your files, edits them, runs commands, and reports back. When the draft looks right:
> deploy to production
One-shot mode (any of the three CLIs)
For quick answers without entering a session:
claude "explain what package.json does in this repo"
codex "write a bash script that rotates log files older than 14 days"
chatgpt "what's the difference between sync and async in Node.js?"
gh copilot suggest "rebase my feature branch on main without merge commits"
Common gotchas
command not foundafter install. Close and reopen your terminal. PATH changes only take effect in new sessions.Execution policyerror on Windows.Set-ExecutionPolicy Bypass -Scope Process -Forcethen re-run.Permission deniedon Linux scripts.chmod +x ./script.shthen rerun.- Netlify deploy shows wrong files. Specify the directory explicitly:
netlify deploy --dir=_site --prod. claudesays API key missing. Either runclaudewith no args (triggers browser OAuth) orecho $ANTHROPIC_API_KEYto confirm it's set.- OpenAI
insufficient_quotaerror. The API tier is separate from ChatGPT Plus, prepay $5 at platform.openai.com/account/billing. - Copilot CLI says "no subscription." A GitHub Pro / Team / Enterprise account doesn't include Copilot, you need the $10/mo Copilot plan or be in the free student/OSS program.
Cheat sheet — the commands you'll use daily
# Start AI sessions
claude # Claude Code interactive
claude "one-shot prompt" # Quick Claude query
codex # OpenAI Codex interactive
chatgpt # ChatGPT streaming terminal
gh copilot suggest "…" # Shell command suggestion
# Inside Claude Code
/help # slash commands
/clear # reset conversation
/mcp list # show connected services
Esc # exit
# Git (used constantly)
git status ; git diff ; git add . ; git commit -m "msg"
# Deploy
netlify deploy # preview URL
netlify deploy --prod # live
netlify deploy --dir=_site --prod # for SSG builds (Eleventy etc.)
Optional — connect Claude to Google Drive, Slack, GitHub via MCP
MCP (Model Context Protocol) servers let Claude read external apps from inside the terminal.
# Google Drive
claude mcp add gdrive -- npx @anthropic/gdrive-mcp
# Slack
claude mcp add slack -- npx @anthropic/slack-mcp
# GitHub (enhanced — reads issues, PRs, etc.)
claude mcp add github -- npx @anthropic/github-mcp
# Playwright (browser automation — screenshots, form-filling)
claude mcp add playwright -- npx @playwright/mcp@latest
# List / remove
claude mcp list
claude mcp remove gdrive
First run of each MCP prompts for OAuth. The tokens live locally; nothing is sent to Anthropic or OpenAI outside the normal API calls.
Why the terminal wins over the web UI
The 30-second argument: your code lives on the filesystem; your git history lives in the terminal; your builds run in the terminal; your deploys happen from the terminal. The web GUI makes you leave where the work happens, describe your problem in isolation, receive a response in isolation, then manually carry that response back to where the work happens. The terminal eliminates the round trip entirely.
On a 50-file refactor, the terminal approach takes 2 minutes. The GUI approach takes 45 minutes of copy-paste-switch-paste-switch-paste.
Side-by-side on one concrete task
Task: "Add a dark mode toggle to my website."
Web GUI (ChatGPT / Claude.ai):
- Open browser → claude.ai (8s)
- "add dark mode toggle" → AI asks for HTML → open file explorer, find
index.html, copy → paste in browser - AI asks for CSS → repeat for
styles.css - AI returns three code blocks → copy HTML → switch to editor → find insertion point → paste
- Copy CSS → find insertion point → paste
- Copy JS → create
toggle.js→ paste → add<script>tag to HTML - Refresh browser, hit a bug, copy the error, paste back, get fix, copy, switch, paste, test again
~20 steps, ~15 window switches, ~130 seconds of pure mechanical copy-paste.
Terminal (Claude Code):
cd ~/my-website && claude> add a dark mode toggle to the site- Press
yto approve the diff.
3 steps, 0 window switches, ~7 seconds of input. Time saved: ~2 minutes per change. Over a workday of 30 changes, that's an hour recovered.
What the terminal gives the model that the GUI can't
- Real filesystem access, every
.md,.njk,.toml,.envin your working directory, read on demand. No more copy-pasting individual files and forgetting one. - Command execution —
git status,npm run build,netlify deploy --prod,curlhealth checks, run and reacted to in the same loop, not copied out and run by you. - Multi-file atomic edits, rename a variable across 40 files, update imports, run tests, revert if anything breaks, one session. Browser UI is one file at a time and loses context each round-trip.
- Full git integration, commits with meaningful messages, branches, PR open, diff review, all native.
- No token bloat from pasted context, CLI streams file contents only when the model asks. Browser burns context on every copy-paste.
Speed, memory, battery
| Metric | Terminal | Web GUI |
|---|---|---|
| Startup | 1–2 s | 5–15 s |
| RAM | 50–150 MB | 300–800 MB per tab |
| Input latency | Instant | 50–100 ms (JS overhead) |
| File read | <1 ms (local) | Manual upload (2–30 s) |
| File write | <1 ms (local) | Copy-paste (10–30 s) |
| Battery hit (1 hr laptop) | ~2 % | ~8–15 % |
Over an 8-hour day of heavy AI use: terminal ~400 MB RAM, minimal battery hit. Browser ~3–5 GB RAM across tabs, significant battery drain.
Multi-file tasks the GUI cannot do
| Task | Terminal | Web GUI |
|---|---|---|
| Rename a function across 30 files | 15 s | 45 min |
| Add a header to 50 HTML pages | 20 s | 2 hrs |
| Update import paths across 100 files after a folder rename | 10 s | impossible |
Find & fix every console.log before deploy |
5 s | 30 min |
| Add error handling to every API call in the project | 30 s | 1 hr |
| Update copyright year in every page footer | 3 s | 20 min |
Web GUI fundamentally has no filesystem access, you'd paste files one at a time, get changes one at a time, apply them one at a time.
Security and audit
| Risk | Terminal | Web GUI |
|---|---|---|
| Browser extensions reading your code / API keys | Not possible | Major, extensions can read all page content |
| Clipboard exposure | Minimal (direct file writes) | Every copy-paste puts code on the clipboard |
| Session hijacking | API key in env var | Session cookie in browser (XSS surface) |
| Data in browser cache | None | Conversations cached in localStorage |
| Audit trail | Shell history (local file) | Proprietary, deletable |
API keys in the terminal are stored in environment variables, never displayed, OS-file-permission protected, invisible to browser extensions, and not sent to analytics or tracking services.
Where the terminal is the only option
- Remote servers & SSH, you can't open claude.ai on a headless server.
ssh prod && claudeis the whole workflow. Read nginx logs, fix config, restart service, one prompt. - Persistent sessions —
tmux new -s ai-session+claude, detach, close SSH, go home. Come back next morning,tmux attach -t ai-session, Claude is still there reporting what it found. - CI/CD —
claude --print "run the release checklist"in a GitHub Action, Vercel hook, or cron job. The browser cannot be scripted. - MCP connectors, Google Drive, Slack, GitHub, Playwright, databases. All terminal-only. The web GUI has zero MCP support.
When the GUI actually wins
- You want an image or voice conversation.
- You're on a Chromebook / iPad where you can't install tools.
- You want to share a conversation link.
- You're drag-dropping a PDF for one-off analysis.
- You're walking or driving and want voice interaction (mobile app).
That's ~10–20 % of professional AI coding work. The terminal handles the other 80–90 % faster, safer, and more reliably.
How Claude Code CLI + Netlify CLI work together for easy administration
Once both are installed and authenticated, the whole admin surface of a website collapses into prompts. Here's the pattern for the most common ops:
Deploy a change end-to-end
cd ~/my-website
claude
> add a privacy policy page that covers cookies and analytics, link it
from the footer, run the build, and deploy a draft to Netlify
Claude writes the page, updates the footer, runs npm run build (or npx @11ty/eleventy), runs netlify deploy, returns the preview URL. You check the preview in a browser, then:
> looks good, deploy to production and ping IndexNow
Add a redirect without touching config files
> add a 301 redirect from /old-page to /new-page in netlify.toml
and verify it works after deploy with curl
Claude edits netlify.toml, deploys, runs curl -I https://site.com/old-page to check for the Location: header. One prompt, proof-of-fix included.
Set environment variables securely
> set a Netlify environment variable SENDGRID_API_KEY to the value
I'll paste next, then redeploy
Claude uses netlify env:set SENDGRID_API_KEY ... (the value stays in the terminal session, never written to a file). Then it triggers a redeploy so the new env is picked up.
Investigate a production issue
> pull the last 3 Netlify deploy logs, summarize any errors, and
suggest a fix for each
netlify api listSiteDeploys returns the deploy history; Claude reads the logs, spots the pattern (build failure, timeout, bad env), and proposes the patch.
Migrate or clone a site
> this folder is a working Eleventy site. Create a new Netlify site
called "alpha-staging", link this folder to it, and do a prod deploy
Claude runs netlify sites:create --name alpha-staging, then netlify link --name alpha-staging, then netlify deploy --dir=_site --prod. Zero clicks in the Netlify dashboard.
Rollback fast
> the last deploy broke the homepage, restore the previous production
deploy
Claude uses netlify api listSiteDeploys to find the previous successful deploy ID, then netlify api restoreSiteDeploy to roll back. ~5 seconds.
The through-line: every Netlify admin action has a CLI equivalent, and every CLI equivalent is something Claude Code can run from a prompt. Once you accept that, the Netlify dashboard becomes a read-only status page and everything mutating happens via prompt.
Cross-promote: pair the terminal with the prompt generators on this site
The terminal gives Claude real filesystem + command access. The jwatte.com tool suite gives Claude the exact thing to do. That pairing is the whole productivity story.
Write better prompts before you hand them to Claude:
- Prompt Enhancer, wraps any prompt in research-backed patterns (ExpertPrompting, OPRO, EmotionPrompt, self-evaluation). Paste your rough instruction, pick an intensity, copy the enhanced version, hand it to
claudeorcodex. Companion post →
Generate ready-to-paste prompts from audit data:
- Mega Analyzer, audits one URL across SEO, schema, E-E-A-T, voice, mobile, performance, AI-search. The output ends in a multi-thousand-word AI fix prompt that names exact file paths and expected score deltas. Pipe that into
claudeand you go from "here's what's broken" to "here's the fix, committed and deployed" in one session. - Site Analyzer, same pattern, 70+ checks, different scoring buckets. Also emits a copy-paste AI fix prompt.
- Batch Compare, up to 10 URLs compared side-by-side with a combined AI prompt covering the whole portfolio. Feed it to Claude and fix 10 sites from one session.
- Link Graph, crawls a site, finds orphans / hubs / dead-ends / noindex pages, and emits an AI fix prompt that proposes exact internal-link additions (source page + destination + anchor text). Paste, tell Claude "apply every recommendation, commit grouped by destination page, deploy", done.
Scaffold new sites from a prompt:
- Single Site Gen, emits a full AI site-build prompt with every best practice (schema, llms.txt, IndexNow, security headers, E-E-A-T signals, WCAG 2.2) baked in.
claude < singlesitegen-prompt.txtand Claude scaffolds the whole thing in~/new-site/. - Monoclone Generator, for when you're spinning up an industry site from a template. Generates the deploy prompt for the whole network.
Generate JSON-LD / schema blocks Claude can drop in directly:
- E-E-A-T Generator, Person / Organization / sameAs / Wikidata / ORCID / rel=me JSON-LD from a single author profile.
- Speakable Generator, SpeakableSpecification JSON-LD for voice + AI-citation.
- FAQ Harvester, pulls every FAQ from the Google top 10, dedupes, emits ready-to-paste FAQPage JSON-LD.
- ItemList / Carousel, Google-compliant ItemList JSON-LD from a URL list.
Audit without leaving the terminal:
- .well-known Audit, 13-file audit of
/.well-known/with copy-paste fix kit. - ai.txt Generator, Spawning-style AI training opt-in/out policy across every bot in the shared registry (robots.txt companion, Netlify/Apache deploy config).
Concrete workflow that ties all of it together:
# 1. Browser: run /tools/mega-analyzer/ on your site, click "Copy AI fix prompt"
# 2. Terminal:
cd ~/my-site
claude
> [paste the Mega Analyzer fix prompt]
# Claude works through every check, edits the files, commits
> run a mobile parity check, then deploy to Netlify prod if pass
# Claude runs /tools/mobile-parity/ mentally, or you paste its URL as input
The tools produce prompts; the terminal consumes prompts. The browser is just the scratchpad where you collect the prompt.
When to reach for which tool
- Editing a real project, multi-file changes, deploys. → Claude Code. It's the only one that reads + writes files end-to-end in your working directory.
- Quick one-off code snippet or explanation. → Codex or
chatgptone-shot mode. Faster, cheaper, no project context needed. - "What's the shell command for X?" → Copilot CLI. Specifically trained for the one-line shell suggestion task.
- GUI preference / on a tablet or Chromebook. → claude.ai/code (browser-based Claude Code), chatgpt.com, VS Code Copilot extension.
The terminal versions are faster for anything that involves your local repo. The GUI versions are better for pure conversation or when you can't install locally.
Related reading
Next up — the follow-on walkthrough: You've Got The Claude Code CLI Installed — Now What? picks up where this post leaves off. Plan mode, CLAUDE.md as a living contract, the slash commands that matter (/btw /fork /rewind /compact /loop /schedule /simplify /batch /powerup /insights /debug /claude-api), Skills vs Rules vs Memory, Auto Mode boundaries, and a Day 0 → Day 7 ramp plan.
Companion coverage from the methodology stack:
- The $97 Launch, Chapter 1 (domain + hosting + deploy). Claude Code + Netlify CLI is the end-state of the workflow that chapter sets up.
- The $20 Dollar Agency, Chapters 5-11 (SEO, schema, keywords). Every audit pattern the Site Analyzer and Mega Analyzer run is something Claude Code can implement in one prompt once the environment is live.
- The $100 Network, Chapter 6 (the provider stack) + Chapter 26 (monitoring at scale). Where the AI terminal fits when you're running more than one site. } | Select-Object -First 1 if ($msiAsset) { $msiPath = Join-Path $script:TempDir $msiAsset.name Invoke-WebRequest -Uri $msiAsset.browser_download_url -OutFile $msiPath -UseBasicParsing Write-OK "Downloaded: $($msiAsset.name)"
Write-Info "Installing silently..."
$msiArgs = "/i "$msiPath" /qn /norestart ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1 ENABLE_PSREMOTING=0 REGISTER_MANIFEST=1 USE_MU=1 ENABLE_MU=1"
Start-Process msiexec.exe -ArgumentList $msiArgs -Wait -NoNewWindow
$installed = $true
Write-OK "PowerShell 7 installed via MSI"
}
}
catch {
Write-Fail "MSI download/install failed: $_"
}
}
if ($installed) { Add-Result "PowerShell 7" "INSTALLED" "Restart terminal to use pwsh.exe" Write-Host "" Write-Tip "After this script finishes, open a NEW terminal and type 'pwsh' to use PS7." Write-Tip "You can also right-click > 'Open in Terminal' for Windows Terminal with PS7." } else { Add-Result "PowerShell 7" "FAILED" "Manual install needed" Write-Warn "Install manually from: https://aka.ms/powershell-release?tag=stable" } } else { Write-Info "Staying on PowerShell $psVer" Add-Result "PowerShell 7" "SKIPPED" "User declined" } }
═══════════════════════════════════════════════════════════════════════════════
PHASE 2 - CORE PREREQUISITES
═══════════════════════════════════════════════════════════════════════════════
Write-Section "PHASE 2: Core Prerequisites"
─── 2a. Execution Policy ─────────────────────────────────────────────────────
Write-Step "Checking execution policy" $policy = Get-ExecutionPolicy -Scope LocalMachine if ($policy -in 'Restricted', 'AllSigned') { if ($isAdmin) { Set-ExecutionPolicy RemoteSigned -Scope LocalMachine -Force Write-OK "Execution policy set to RemoteSigned" } else { Write-Warn "Execution policy is '$policy' - run as Admin to change it" } } else { Write-OK "Execution policy is '$policy'" } Add-Result "Execution Policy" "OK" $policy
─── 2b. Package Manager (Chocolatey) ─────────────────────────────────────────
Write-Step "Checking Chocolatey package manager" if (Test-CmdExists 'choco') { $chocoVer = choco --version 2>$null Write-OK "Chocolatey already installed ($chocoVer)" Add-Result "Chocolatey" "PRESENT" "v$chocoVer" } else { Write-Info "Chocolatey is used to install many developer tools automatically." if (Get-YesNo "Install Chocolatey?") { try { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) Refresh-Path choco feature enable -n allowGlobalConfirmation 2>$null | Out-Null Write-OK "Chocolatey installed" Add-Result "Chocolatey" "INSTALLED" "" } catch { Write-Fail "Chocolatey install failed: $" Add-Result "Chocolatey" "FAILED" "$" } } else { Add-Result "Chocolatey" "SKIPPED" "User declined" } }
─── 2c. Git for Windows ──────────────────────────────────────────────────────
Write-Step "Checking Git for Windows" Refresh-Path if (Test-CmdExists 'git') { $gitVer = (git --version) -replace 'git version ', '' Write-OK "Git already installed ($gitVer)" Write-Info "Git Bash: $env:ProgramFiles\Git\bin\bash.exe" Add-Result "Git" "PRESENT" "v$gitVer" } else { Write-Info "Git is REQUIRED for Claude Code and most AI terminal tools." Write-Info "It also provides Git Bash which Claude Code uses on Windows." if (Get-YesNo "Install Git for Windows?" $true) { $gitInstalled = $false
Try winget first
if (Test-CmdExists 'winget') { Write-Info "Installing via winget..." try { winget install --id Git.Git --accept-source-agreements --accept-package-agreements --silent 2>$null if ($LASTEXITCODE -eq 0) { $gitInstalled = $true } } catch { Write-Info "winget install failed: $_ - trying next method..." } }
Try Chocolatey
if (-not $gitInstalled -and (Test-CmdExists 'choco')) { Write-Info "Installing via Chocolatey..." try { choco install git --yes --no-progress $gitInstalled = $true } catch { Write-Info "Chocolatey install failed: $_ - trying direct download..." } }
Direct download fallback (auto-detect latest version from GitHub API)
if (-not $gitInstalled) { Write-Info "Downloading Git from GitHub (auto-detecting latest release)..." try { $gitRelease = Invoke-RestMethod 'https://api.github.com/repos/git-for-windows/git/releases/latest' $gitAsset = $gitRelease.assets | Where-Object { $_.name -match '64-bit.exe
Windows (PowerShell, as Administrator)
# One-time: allow the current session to run scripts
Set-ExecutionPolicy Bypass -Scope Process -Force
# Install Chocolatey (Windows package manager)
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
# Install runtimes + utilities
choco install -y git nodejs-lts python312 ripgrep jq fd 7zip imagemagick pwsh
# Verify
node --version ; npm --version ; python --version ; git --version
Linux (Bash, Ubuntu / Debian / Mint)
# Update + install runtimes + utilities
sudo apt update
sudo apt install -y git curl build-essential python3 python3-pip python3-venv ripgrep jq fd-find imagemagick
# Install Node.js LTS via NodeSource (the apt package is usually out of date)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version ; npm --version ; python3 --version ; git --version
Linux (Bash, Fedora / RHEL / Rocky)
sudo dnf install -y git curl python3 python3-pip ripgrep jq fd-find ImageMagick
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs
Close the terminal and open a new one after runtime installs. PATH updates only take effect in fresh sessions.
Step 2 — Install Claude Code
Anthropic's AI coding terminal. Reads your repo, edits files, runs commands, opens PRs.
Windows (PowerShell)
# Official installer (puts 'claude' on your PATH)
irm https://claude.ai/install.ps1 | iex
Linux (Bash)
curl -fsSL https://claude.ai/install.sh | sh
Both platforms verify:
claude --version
Authenticate once by just running claude in any directory, it opens a browser to claude.ai, you click Authorize, and you're done. No API key needed for the browser flow. If you prefer an API key (required for CI):
# Windows (persists across sessions):
[Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-your-key-here", "User")
# Linux:
echo 'export ANTHROPIC_API_KEY="sk-ant-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Get an API key at console.anthropic.com/settings/keys.
Step 3 — Install OpenAI Codex / ChatGPT CLI
The official OpenAI CLI for coding (Codex) and a quick ChatGPT terminal for one-shot questions.
Both platforms
# Official OpenAI Codex CLI (Node-based)
npm install -g @openai/codex
# Verify
codex --version
For a lightweight ChatGPT streaming terminal (python-based alternative):
pip install chatgpt-cli
# One-shot
chatgpt "explain rsync incremental backups in one paragraph"
# Interactive
chatgpt
Authenticate by setting your OpenAI key. Get one at platform.openai.com/api-keys (requires a paid account, $5 prepaid is enough).
# Windows
[Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-your-key-here", "User")
# Linux
echo 'export OPENAI_API_KEY="sk-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Step 4 — Install GitHub Copilot CLI
Copilot CLI lives inside the gh (GitHub) CLI as an extension.
Windows (PowerShell)
choco install -y gh
gh auth login # web browser flow — select GitHub.com + browser
gh extension install github/gh-copilot
Linux (Bash, Ubuntu / Debian / Mint)
# Add GitHub CLI apt repo
type -p curl >/dev/null || sudo apt install -y curl
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
gh auth login # choose GitHub.com → web browser → paste one-time code
gh extension install github/gh-copilot
Linux (Bash, Fedora / RHEL)
sudo dnf install -y 'dnf-command(config-manager)'
sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
sudo dnf install -y gh
gh auth login
gh extension install github/gh-copilot
Verify:
gh copilot suggest "find all PNG files larger than 1MB in current directory"
gh copilot explain "tar -xzf archive.tar.gz -C /opt/"
Copilot CLI requires a Copilot subscription ($10/month individual, free for students / verified OSS maintainers).
Step 5 — Install Netlify CLI
For deploying sites directly from the terminal after Claude / Codex writes them.
Both platforms
npm install -g netlify-cli
netlify --version
netlify login # browser opens → Authorize → back to terminal
Verify auth:
netlify status
Step 6 — Install Python AI SDKs (optional but useful)
For scripting against the APIs directly (no terminal UI).
# Both platforms — use pip (Linux may need python3 -m pip)
pip install anthropic openai google-generativeai fastmcp mcp requests httpx pandas
Step 7 — Your first session
From your project directory:
cd ~/my-website # or: cd $env:USERPROFILE\my-website on Windows
claude
Then at the Claude prompt:
> give me a summary of this project
> add a dark-mode toggle to the header, commit, and show me the diff
> deploy a draft to Netlify so I can preview it
Claude reads your files, edits them, runs commands, and reports back. When the draft looks right:
> deploy to production
One-shot mode (any of the three CLIs)
For quick answers without entering a session:
claude "explain what package.json does in this repo"
codex "write a bash script that rotates log files older than 14 days"
chatgpt "what's the difference between sync and async in Node.js?"
gh copilot suggest "rebase my feature branch on main without merge commits"
Common gotchas
command not foundafter install. Close and reopen your terminal. PATH changes only take effect in new sessions.Execution policyerror on Windows.Set-ExecutionPolicy Bypass -Scope Process -Forcethen re-run.Permission deniedon Linux scripts.chmod +x ./script.shthen rerun.- Netlify deploy shows wrong files. Specify the directory explicitly:
netlify deploy --dir=_site --prod. claudesays API key missing. Either runclaudewith no args (triggers browser OAuth) orecho $ANTHROPIC_API_KEYto confirm it's set.- OpenAI
insufficient_quotaerror. The API tier is separate from ChatGPT Plus, prepay $5 at platform.openai.com/account/billing. - Copilot CLI says "no subscription." A GitHub Pro / Team / Enterprise account doesn't include Copilot, you need the $10/mo Copilot plan or be in the free student/OSS program.
Cheat sheet — the commands you'll use daily
# Start AI sessions
claude # Claude Code interactive
claude "one-shot prompt" # Quick Claude query
codex # OpenAI Codex interactive
chatgpt # ChatGPT streaming terminal
gh copilot suggest "…" # Shell command suggestion
# Inside Claude Code
/help # slash commands
/clear # reset conversation
/mcp list # show connected services
Esc # exit
# Git (used constantly)
git status ; git diff ; git add . ; git commit -m "msg"
# Deploy
netlify deploy # preview URL
netlify deploy --prod # live
netlify deploy --dir=_site --prod # for SSG builds (Eleventy etc.)
Optional — connect Claude to Google Drive, Slack, GitHub via MCP
MCP (Model Context Protocol) servers let Claude read external apps from inside the terminal.
# Google Drive
claude mcp add gdrive -- npx @anthropic/gdrive-mcp
# Slack
claude mcp add slack -- npx @anthropic/slack-mcp
# GitHub (enhanced — reads issues, PRs, etc.)
claude mcp add github -- npx @anthropic/github-mcp
# Playwright (browser automation — screenshots, form-filling)
claude mcp add playwright -- npx @playwright/mcp@latest
# List / remove
claude mcp list
claude mcp remove gdrive
First run of each MCP prompts for OAuth. The tokens live locally; nothing is sent to Anthropic or OpenAI outside the normal API calls.
Why the terminal wins over the web UI
The 30-second argument: your code lives on the filesystem; your git history lives in the terminal; your builds run in the terminal; your deploys happen from the terminal. The web GUI makes you leave where the work happens, describe your problem in isolation, receive a response in isolation, then manually carry that response back to where the work happens. The terminal eliminates the round trip entirely.
On a 50-file refactor, the terminal approach takes 2 minutes. The GUI approach takes 45 minutes of copy-paste-switch-paste-switch-paste.
Side-by-side on one concrete task
Task: "Add a dark mode toggle to my website."
Web GUI (ChatGPT / Claude.ai):
- Open browser → claude.ai (8s)
- "add dark mode toggle" → AI asks for HTML → open file explorer, find
index.html, copy → paste in browser - AI asks for CSS → repeat for
styles.css - AI returns three code blocks → copy HTML → switch to editor → find insertion point → paste
- Copy CSS → find insertion point → paste
- Copy JS → create
toggle.js→ paste → add<script>tag to HTML - Refresh browser, hit a bug, copy the error, paste back, get fix, copy, switch, paste, test again
~20 steps, ~15 window switches, ~130 seconds of pure mechanical copy-paste.
Terminal (Claude Code):
cd ~/my-website && claude> add a dark mode toggle to the site- Press
yto approve the diff.
3 steps, 0 window switches, ~7 seconds of input. Time saved: ~2 minutes per change. Over a workday of 30 changes, that's an hour recovered.
What the terminal gives the model that the GUI can't
- Real filesystem access, every
.md,.njk,.toml,.envin your working directory, read on demand. No more copy-pasting individual files and forgetting one. - Command execution —
git status,npm run build,netlify deploy --prod,curlhealth checks, run and reacted to in the same loop, not copied out and run by you. - Multi-file atomic edits, rename a variable across 40 files, update imports, run tests, revert if anything breaks, one session. Browser UI is one file at a time and loses context each round-trip.
- Full git integration, commits with meaningful messages, branches, PR open, diff review, all native.
- No token bloat from pasted context, CLI streams file contents only when the model asks. Browser burns context on every copy-paste.
Speed, memory, battery
| Metric | Terminal | Web GUI |
|---|---|---|
| Startup | 1–2 s | 5–15 s |
| RAM | 50–150 MB | 300–800 MB per tab |
| Input latency | Instant | 50–100 ms (JS overhead) |
| File read | <1 ms (local) | Manual upload (2–30 s) |
| File write | <1 ms (local) | Copy-paste (10–30 s) |
| Battery hit (1 hr laptop) | ~2 % | ~8–15 % |
Over an 8-hour day of heavy AI use: terminal ~400 MB RAM, minimal battery hit. Browser ~3–5 GB RAM across tabs, significant battery drain.
Multi-file tasks the GUI cannot do
| Task | Terminal | Web GUI |
|---|---|---|
| Rename a function across 30 files | 15 s | 45 min |
| Add a header to 50 HTML pages | 20 s | 2 hrs |
| Update import paths across 100 files after a folder rename | 10 s | impossible |
Find & fix every console.log before deploy |
5 s | 30 min |
| Add error handling to every API call in the project | 30 s | 1 hr |
| Update copyright year in every page footer | 3 s | 20 min |
Web GUI fundamentally has no filesystem access, you'd paste files one at a time, get changes one at a time, apply them one at a time.
Security and audit
| Risk | Terminal | Web GUI |
|---|---|---|
| Browser extensions reading your code / API keys | Not possible | Major, extensions can read all page content |
| Clipboard exposure | Minimal (direct file writes) | Every copy-paste puts code on the clipboard |
| Session hijacking | API key in env var | Session cookie in browser (XSS surface) |
| Data in browser cache | None | Conversations cached in localStorage |
| Audit trail | Shell history (local file) | Proprietary, deletable |
API keys in the terminal are stored in environment variables, never displayed, OS-file-permission protected, invisible to browser extensions, and not sent to analytics or tracking services.
Where the terminal is the only option
- Remote servers & SSH, you can't open claude.ai on a headless server.
ssh prod && claudeis the whole workflow. Read nginx logs, fix config, restart service, one prompt. - Persistent sessions —
tmux new -s ai-session+claude, detach, close SSH, go home. Come back next morning,tmux attach -t ai-session, Claude is still there reporting what it found. - CI/CD —
claude --print "run the release checklist"in a GitHub Action, Vercel hook, or cron job. The browser cannot be scripted. - MCP connectors, Google Drive, Slack, GitHub, Playwright, databases. All terminal-only. The web GUI has zero MCP support.
When the GUI actually wins
- You want an image or voice conversation.
- You're on a Chromebook / iPad where you can't install tools.
- You want to share a conversation link.
- You're drag-dropping a PDF for one-off analysis.
- You're walking or driving and want voice interaction (mobile app).
That's ~10–20 % of professional AI coding work. The terminal handles the other 80–90 % faster, safer, and more reliably.
How Claude Code CLI + Netlify CLI work together for easy administration
Once both are installed and authenticated, the whole admin surface of a website collapses into prompts. Here's the pattern for the most common ops:
Deploy a change end-to-end
cd ~/my-website
claude
> add a privacy policy page that covers cookies and analytics, link it
from the footer, run the build, and deploy a draft to Netlify
Claude writes the page, updates the footer, runs npm run build (or npx @11ty/eleventy), runs netlify deploy, returns the preview URL. You check the preview in a browser, then:
> looks good, deploy to production and ping IndexNow
Add a redirect without touching config files
> add a 301 redirect from /old-page to /new-page in netlify.toml
and verify it works after deploy with curl
Claude edits netlify.toml, deploys, runs curl -I https://site.com/old-page to check for the Location: header. One prompt, proof-of-fix included.
Set environment variables securely
> set a Netlify environment variable SENDGRID_API_KEY to the value
I'll paste next, then redeploy
Claude uses netlify env:set SENDGRID_API_KEY ... (the value stays in the terminal session, never written to a file). Then it triggers a redeploy so the new env is picked up.
Investigate a production issue
> pull the last 3 Netlify deploy logs, summarize any errors, and
suggest a fix for each
netlify api listSiteDeploys returns the deploy history; Claude reads the logs, spots the pattern (build failure, timeout, bad env), and proposes the patch.
Migrate or clone a site
> this folder is a working Eleventy site. Create a new Netlify site
called "alpha-staging", link this folder to it, and do a prod deploy
Claude runs netlify sites:create --name alpha-staging, then netlify link --name alpha-staging, then netlify deploy --dir=_site --prod. Zero clicks in the Netlify dashboard.
Rollback fast
> the last deploy broke the homepage, restore the previous production
deploy
Claude uses netlify api listSiteDeploys to find the previous successful deploy ID, then netlify api restoreSiteDeploy to roll back. ~5 seconds.
The through-line: every Netlify admin action has a CLI equivalent, and every CLI equivalent is something Claude Code can run from a prompt. Once you accept that, the Netlify dashboard becomes a read-only status page and everything mutating happens via prompt.
Cross-promote: pair the terminal with the prompt generators on this site
The terminal gives Claude real filesystem + command access. The jwatte.com tool suite gives Claude the exact thing to do. That pairing is the whole productivity story.
Write better prompts before you hand them to Claude:
- Prompt Enhancer, wraps any prompt in research-backed patterns (ExpertPrompting, OPRO, EmotionPrompt, self-evaluation). Paste your rough instruction, pick an intensity, copy the enhanced version, hand it to
claudeorcodex. Companion post →
Generate ready-to-paste prompts from audit data:
- Mega Analyzer, audits one URL across SEO, schema, E-E-A-T, voice, mobile, performance, AI-search. The output ends in a multi-thousand-word AI fix prompt that names exact file paths and expected score deltas. Pipe that into
claudeand you go from "here's what's broken" to "here's the fix, committed and deployed" in one session. - Site Analyzer, same pattern, 70+ checks, different scoring buckets. Also emits a copy-paste AI fix prompt.
- Batch Compare, up to 10 URLs compared side-by-side with a combined AI prompt covering the whole portfolio. Feed it to Claude and fix 10 sites from one session.
- Link Graph, crawls a site, finds orphans / hubs / dead-ends / noindex pages, and emits an AI fix prompt that proposes exact internal-link additions (source page + destination + anchor text). Paste, tell Claude "apply every recommendation, commit grouped by destination page, deploy", done.
Scaffold new sites from a prompt:
- Single Site Gen, emits a full AI site-build prompt with every best practice (schema, llms.txt, IndexNow, security headers, E-E-A-T signals, WCAG 2.2) baked in.
claude < singlesitegen-prompt.txtand Claude scaffolds the whole thing in~/new-site/. - Monoclone Generator, for when you're spinning up an industry site from a template. Generates the deploy prompt for the whole network.
Generate JSON-LD / schema blocks Claude can drop in directly:
- E-E-A-T Generator, Person / Organization / sameAs / Wikidata / ORCID / rel=me JSON-LD from a single author profile.
- Speakable Generator, SpeakableSpecification JSON-LD for voice + AI-citation.
- FAQ Harvester, pulls every FAQ from the Google top 10, dedupes, emits ready-to-paste FAQPage JSON-LD.
- ItemList / Carousel, Google-compliant ItemList JSON-LD from a URL list.
Audit without leaving the terminal:
- .well-known Audit, 13-file audit of
/.well-known/with copy-paste fix kit. - ai.txt Generator, Spawning-style AI training opt-in/out policy across every bot in the shared registry (robots.txt companion, Netlify/Apache deploy config).
Concrete workflow that ties all of it together:
# 1. Browser: run /tools/mega-analyzer/ on your site, click "Copy AI fix prompt"
# 2. Terminal:
cd ~/my-site
claude
> [paste the Mega Analyzer fix prompt]
# Claude works through every check, edits the files, commits
> run a mobile parity check, then deploy to Netlify prod if pass
# Claude runs /tools/mobile-parity/ mentally, or you paste its URL as input
The tools produce prompts; the terminal consumes prompts. The browser is just the scratchpad where you collect the prompt.
When to reach for which tool
- Editing a real project, multi-file changes, deploys. → Claude Code. It's the only one that reads + writes files end-to-end in your working directory.
- Quick one-off code snippet or explanation. → Codex or
chatgptone-shot mode. Faster, cheaper, no project context needed. - "What's the shell command for X?" → Copilot CLI. Specifically trained for the one-line shell suggestion task.
- GUI preference / on a tablet or Chromebook. → claude.ai/code (browser-based Claude Code), chatgpt.com, VS Code Copilot extension.
The terminal versions are faster for anything that involves your local repo. The GUI versions are better for pure conversation or when you can't install locally.
Related reading
Next up — the follow-on walkthrough: You've Got The Claude Code CLI Installed — Now What? picks up where this post leaves off. Plan mode, CLAUDE.md as a living contract, the slash commands that matter (/btw /fork /rewind /compact /loop /schedule /simplify /batch /powerup /insights /debug /claude-api), Skills vs Rules vs Memory, Auto Mode boundaries, and a Day 0 → Day 7 ramp plan.
Companion coverage from the methodology stack:
- The $97 Launch, Chapter 1 (domain + hosting + deploy). Claude Code + Netlify CLI is the end-state of the workflow that chapter sets up.
- The $20 Dollar Agency, Chapters 5-11 (SEO, schema, keywords). Every audit pattern the Site Analyzer and Mega Analyzer run is something Claude Code can implement in one prompt once the environment is live.
- The $100 Network, Chapter 6 (the provider stack) + Chapter 26 (monitoring at scale). Where the AI terminal fits when you're running more than one site. -and $.name -notmatch 'portable' } | Select-Object -First 1 if ($gitAsset) { $gitInstaller = Join-Path $script:TempDir $gitAsset.name $dlOk = Invoke-Download -Uri $gitAsset.browser_download_url -OutFile $gitInstaller -Description "Git for Windows ($($gitAsset.name))" if (-not $dlOk) { throw "Download failed" } $gitArgs = '/VERYSILENT', '/NORESTART', '/SP-', '/CLOSEAPPLICATIONS' Start-Process -FilePath $gitInstaller -ArgumentList $gitArgs -Wait -NoNewWindow $gitInstalled = $true } else { Write-Fail "Could not find Git installer asset in release" } } catch { Write-Fail "Git download failed: $" } }
Refresh-Path if (Test-CmdExists 'git') { Write-OK "Git installed: $(git --version)" Add-Result "Git" "INSTALLED" (git --version) } else { Write-Fail "Git not found after install attempt" Add-Result "Git" "FAILED" "Not on PATH" } } else { Write-Warn "Skipping Git - Claude Code WILL NOT WORK without it!" Add-Result "Git" "SKIPPED" "User declined (WARNING)" } }
─── 2d. Node.js ──────────────────────────────────────────────────────────────
Write-Step "Checking Node.js" Refresh-Path if (Test-CmdExists 'node') { $nodeVer = node --version 2>$null Write-OK "Node.js already installed ($nodeVer)" if (Test-CmdExists 'npm') { Write-OK "npm $(npm --version 2>$null)" } if (Test-CmdExists 'npx') { Write-OK "npx available" } Add-Result "Node.js" "PRESENT" $nodeVer } else { Write-Info "Node.js is required for Claude Code, npm packages, and MCP servers." if (Get-YesNo "Install Node.js LTS?" $true) { $nodeInstalled = $false
if (Test-CmdExists 'winget') { Write-Info "Installing via winget..." try { winget install --id OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent 2>$null if ($LASTEXITCODE -eq 0) { $nodeInstalled = $true } } catch { Write-Info "winget install failed: $_ - trying next method..." } }
if (-not $nodeInstalled -and (Test-CmdExists 'choco')) { Write-Info "Installing via Chocolatey..." try { choco install nodejs-lts --yes --no-progress $nodeInstalled = $true } catch { Write-Info "Chocolatey install failed: $_ - trying direct download..." } }
if (-not $nodeInstalled) {
Write-Info "Downloading from nodejs.org..."
try {
$nodeVersions = Invoke-RestMethod 'https://nodejs.org/dist/index.json'
$latestLts = $nodeVersions | Where-Object { $.lts -ne $false } | Select-Object -First 1
$ver = $latestLts.version
$msi = "node-$ver-x64.msi"
$url = "https://nodejs.org/dist/$ver/$msi"
$installer = Join-Path $script:TempDir $msi
Invoke-WebRequest -Uri $url -OutFile $installer -UseBasicParsing
Start-Process msiexec.exe -ArgumentList "/i "$installer" /qn /norestart" -Wait -NoNewWindow
}
catch {
Write-Fail "Node.js download failed: $"
}
}
Refresh-Path if (Test-CmdExists 'node') { Write-OK "Node.js $(node --version 2>$null) installed" Add-Result "Node.js" "INSTALLED" (node --version 2>$null) } else { Write-Fail "Node.js not found after install" Add-Result "Node.js" "FAILED" "Not on PATH" } } else { Add-Result "Node.js" "SKIPPED" "User declined" } }
─── 2e. Python ───────────────────────────────────────────────────────────────
Write-Step "Checking Python" Refresh-Path if (Test-CmdExists 'python') { $pyVer = python --version 2>&1 Write-OK "Python already installed ($pyVer)" Add-Result "Python" "PRESENT" "$pyVer" } else { Write-Info "Python is used for MCP server development, data processing," Write-Info "security tools, and many Claude Code plugins." if (Get-YesNo "Install Python $($script:PythonVersion)?" $true) { $pyInstalled = $false
if (Test-CmdExists 'winget') { Write-Info "Installing Python $($script:PythonVersion) via winget..." try { winget install --id $script:PythonWingetId --accept-source-agreements --accept-package-agreements --silent 2>$null if ($LASTEXITCODE -eq 0) { $pyInstalled = $true } } catch { Write-Info "winget install failed: $_ - trying next method..." } }
if (-not $pyInstalled -and (Test-CmdExists 'choco')) { Write-Info "Installing Python $($script:PythonVersion) via Chocolatey..." try { choco install $script:PythonChocoId --yes --no-progress $pyInstalled = $true } catch { Write-Info "Chocolatey install failed: $_" } }
Refresh-Path if (Test-CmdExists 'python') { Write-OK "Python $(python --version 2>&1) installed" python -m pip install --upgrade pip --quiet 2>$null Write-OK "pip upgraded" Add-Result "Python" "INSTALLED" (python --version 2>&1) } else { Write-Fail "Python not found after install" Add-Result "Python" "FAILED" "Not on PATH" } } else { Add-Result "Python" "SKIPPED" "User declined" } }
─── 2f. Additional CLI tools ─────────────────────────────────────────────────
Write-Step "Checking additional CLI tools"
$cliTools = @( @{ Name = "ripgrep"; Cmd = "rg"; Desc = "Ultra-fast code search (used by Claude Code)" }, @{ Name = "jq"; Cmd = "jq"; Desc = "JSON processor for API responses" }, @{ Name = "fd"; Cmd = "fd"; Desc = "Fast file finder" }, @{ Name = "7zip"; Cmd = "7z"; Desc = "Archive utility" }, @{ Name = "imagemagick"; Cmd = "magick"; Desc = "Image processing (convert, resize, etc.)" }, @{ Name = "ghostscript"; Cmd = "gswin64c"; Desc = "PDF/PostScript engine (required by ImageMagick for PDFs)" }, @{ Name = "ffmpeg"; Cmd = "ffmpeg"; Desc = "Video/audio processing and media conversion" }, @{ Name = "bat"; Cmd = "bat"; Desc = "Syntax-highlighted file viewer (better cat)" }, @{ Name = "fzf"; Cmd = "fzf"; Desc = "Fuzzy finder for files, history, and commands" }, @{ Name = "yq"; Cmd = "yq"; Desc = "YAML processor (like jq but for YAML)" }, @{ Name = "tree"; Cmd = "tree"; Desc = "Directory structure viewer" }, @{ Name = "delta"; Cmd = "delta"; Desc = "Enhanced git diff viewer with syntax highlighting" }, @{ Name = "shellcheck"; Cmd = "shellcheck"; Desc = "Shell script linter and validator" } )
foreach ($tool in $cliTools) { Refresh-Path if (Test-CmdExists $tool.Cmd) { Write-OK "$($tool.Name) is installed" Add-Result $tool.Name "PRESENT" "" } else { Write-Info "$($tool.Name): $($tool.Desc)" if (Get-YesNo "Install $($tool.Name)?") { if (Test-CmdExists 'choco') { try { choco install $tool.Name --yes --no-progress 2>$null Refresh-Path if (Test-CmdExists $tool.Cmd) { Write-OK "$($tool.Name) installed" Add-Result $tool.Name "INSTALLED" "" } else { Write-Warn "$($tool.Name) installed but not yet on PATH" Add-Result $tool.Name "INSTALLED" "Restart terminal for PATH" } } catch { Write-Fail "$($tool.Name) install failed: $" Add-Result $tool.Name "FAILED" "$" } } elseif (Test-CmdExists 'winget') { try { $wingetIds = @{ "ripgrep" = "BurntSushi.ripgrep.MSVC" "jq" = "jqlang.jq" "fd" = "sharkdp.fd" "7zip" = "7zip.7zip" "imagemagick" = "ImageMagick.ImageMagick" "ghostscript" = "ArtifexSoftware.GhostScript" "ffmpeg" = "Gyan.FFmpeg" "bat" = "sharkdp.bat" "fzf" = "junegunn.fzf" "yq" = "MikeFarah.yq" "tree" = "" "delta" = "dandavison.delta" "shellcheck" = "koalaman.shellcheck" } $id = $wingetIds[$tool.Name] if ($id) { winget install --id $id --accept-source-agreements --accept-package-agreements --silent 2>$null } Refresh-Path Add-Result $tool.Name "INSTALLED" "via winget" } catch { Add-Result $tool.Name "FAILED" "$_" } } else { Write-Warn "No package manager available. Install $($tool.Name) manually." Add-Result $tool.Name "SKIPPED" "No package manager" } } else { Add-Result $tool.Name "SKIPPED" "User declined" } } }
─── 2g. Python packages ──────────────────────────────────────────────────────
Write-Step "Python package libraries" if (Test-CmdExists 'python') { Write-Info "These packages enhance what Claude Code and AI tools can do:" Write-Info " Data: numpy, pandas, polars, matplotlib, openpyxl" Write-Info " Web: requests, httpx, beautifulsoup4" Write-Info " AI: anthropic, openai, fastmcp, mcp" Write-Info " Dev: pydantic, rich, pyyaml, python-dotenv"
if (Get-YesNo "Install recommended Python packages?") { $packages = @( 'numpy', 'pandas', 'polars', 'matplotlib', 'openpyxl', 'requests', 'httpx', 'beautifulsoup4', 'lxml', 'anthropic', 'openai', 'fastmcp', 'mcp', 'pydantic', 'rich', 'pyyaml', 'python-dotenv', 'Pillow', 'chardet', 'tabulate', 'jsonlines' ) try { $pkgList = $packages -join ' ' python -m pip install $pkgList --quiet 2>&1 | Out-Null Write-OK "Installed $($packages.Count) Python packages" Add-Result "Python Packages" "INSTALLED" "$($packages.Count) packages" } catch { Write-Warn "Some packages may have failed: $" Add-Result "Python Packages" "PARTIAL" "$" } } else { Add-Result "Python Packages" "SKIPPED" "" }
uv package manager
if (-not (Test-CmdExists 'uv')) { Write-Info "uv is a fast Python package manager used by MCP servers." if (Get-YesNo "Install uv?") { try { Invoke-RestMethod https://astral.sh/uv/install.ps1 | Invoke-Expression Refresh-Path Write-OK "uv installed" Add-Result "uv" "INSTALLED" "" } catch { Write-Fail "uv install failed: $" Add-Result "uv" "FAILED" "$" } } } else { Write-OK "uv already installed" Add-Result "uv" "PRESENT" "" } }
═══════════════════════════════════════════════════════════════════════════════
PHASE 3 - AI TERMINAL INSTALLATIONS
═══════════════════════════════════════════════════════════════════════════════
Enter-Phase "AI Terminal Solutions" Write-Section "PHASE 3: AI Terminal Solutions"
Write-Host "" Write-Host " Which AI terminal solutions would you like to set up?" -ForegroundColor White Write-Host "" Write-Host " ┌──────────────────────────────────────────────────────────────┐" -ForegroundColor DarkCyan Write-Host " │ 1. Claude Code (Anthropic) │" -ForegroundColor DarkCyan Write-Host " │ Best for: Deep code understanding, multi-file edits, │" -ForegroundColor Gray Write-Host " │ agentic coding, MCP servers, security research │" -ForegroundColor Gray Write-Host " │ Needs: Git Bash, Node.js, Anthropic API key or login │" -ForegroundColor Gray Write-Host " │ │" -ForegroundColor DarkCyan Write-Host " │ 2. ChatGPT Terminal (OpenAI) │" -ForegroundColor DarkCyan Write-Host " │ Best for: Quick questions, brainstorming, general AI │" -ForegroundColor Gray Write-Host " │ chat, streaming responses, session history │" -ForegroundColor Gray Write-Host " │ Needs: OpenAI API key │" -ForegroundColor Gray Write-Host " │ │" -ForegroundColor DarkCyan Write-Host " │ 3. GitHub Copilot CLI (GitHub) │" -ForegroundColor DarkCyan Write-Host " │ Best for: Shell command suggestions, git operations, │" -ForegroundColor Gray Write-Host " │ explaining commands, GitHub integration │" -ForegroundColor Gray Write-Host " │ Needs: GitHub account with Copilot subscription │" -ForegroundColor Gray Write-Host " └──────────────────────────────────────────────────────────────┘" -ForegroundColor DarkCyan Write-Host ""
$installClaude = Get-YesNo "Install Claude Code?" $true $installChatGPT = Get-YesNo "Install ChatGPT Terminal?" $true $installCopilot = Get-YesNo "Install GitHub Copilot CLI?" $true
─── 3a. Claude Code ──────────────────────────────────────────────────────────
if ($installClaude) { Write-Section "Installing Claude Code"
Check for existing install
Refresh-Path $claudeExists = (Test-CmdExists 'claude') -or (Test-Path "$env:USERPROFILE.local\bin\claude.exe")
if ($claudeExists) { Write-OK "Claude Code already installed" try { $ccVer = claude --version 2>$null Write-OK "Version: $ccVer" } catch { Write-Info "Could not retrieve version" } Add-Result "Claude Code" "PRESENT" "" } else { Write-Step "Installing Claude Code..."
Method 1: Use existing Install-ClaudeCode.ps1 if available
$ccScript = Join-Path $ScriptsDir "Install-ClaudeCode.ps1" if (Test-Path $ccScript) { Write-Info "Found Install-ClaudeCode.ps1 - using your custom installer" try { & $ccScript Refresh-Path Add-Result "Claude Code" "INSTALLED" "via Install-ClaudeCode.ps1" } catch { Write-Warn "Custom installer had an issue: $_" Write-Info "Trying the official method..." } }
Method 2: Official native installer
if (-not (Test-CmdExists 'claude')) { Write-Info "Using official native installer..." try { $installScript = Invoke-RestMethod -Uri "https://claude.ai/install.ps1" -UseBasicParsing Invoke-Expression $installScript Refresh-Path } catch { Write-Warn "Native installer issue: $_" } }
Method 3: npm fallback
if (-not (Test-CmdExists 'claude') -and (Test-CmdExists 'npm')) { Write-Info "Trying npm install as fallback..." try { npm install -g @anthropic-ai/claude-code 2>&1 | Out-Host Refresh-Path } catch { Write-Info "npm fallback also failed: $_" } }
Final check
$env:Path += ";$env:USERPROFILE.local\bin" if (Test-CmdExists 'claude') { Write-OK "Claude Code installed successfully!" try { Write-OK "Version: $(claude --version 2>$null)" } catch {} Add-Result "Claude Code" "INSTALLED" "" } else { Write-Fail "Claude Code could not be installed automatically" Write-Tip "Try manually: irm https://claude.ai/install.ps1 | iex" Add-Result "Claude Code" "FAILED" "Manual install needed" } }
Run Setup-ClaudeCodeEnv.ps1 if available
$envScript = Join-Path $ScriptsDir "Setup-ClaudeCodeEnv.ps1" if (Test-Path $envScript) { Write-Host "" if (Get-YesNo "Run Setup-ClaudeCodeEnv.ps1 (Python libs, CLI tools, Git config)?") { Write-Step "Running Claude Code environment setup..." try { & $envScript Add-Result "Claude Env Setup" "RAN" "" } catch { Write-Warn "Env setup error: $" Add-Result "Claude Env Setup" "ERROR" "$" } } }
Run Install-ClaudePlugins.ps1 if available
$pluginScript = Join-Path $ScriptsDir "Install-ClaudePlugins.ps1" if (Test-Path $pluginScript) { Write-Host "" if (Get-YesNo "Run Install-ClaudePlugins.ps1 (security, frontend, playground, MCP)?") { Write-Step "Installing Claude Code plugins..." try { & $pluginScript Add-Result "Claude Plugins" "RAN" "" } catch { Write-Warn "Plugin install error: $" Add-Result "Claude Plugins" "ERROR" "$" } } }
Run Install-PlaygroundPlugin.ps1 if available
$pgScript = Join-Path $ScriptsDir "Install-PlaygroundPlugin.ps1" if (Test-Path $pgScript) { Write-Host "" if (Get-YesNo "Run Install-PlaygroundPlugin.ps1?") { Write-Step "Installing Playground plugin..." try { & $pgScript Add-Result "Playground Plugin" "RAN" "" } catch { Write-Warn "Playground plugin error: $" Add-Result "Playground Plugin" "ERROR" "$" } } }
Configure Git Bash path for Claude Code
Write-Step "Configuring Claude Code settings..." $gitBashExe = "$env:ProgramFiles\Git\bin\bash.exe" $claudeSettingsDir = Join-Path $env:USERPROFILE ".claude" $claudeSettingsFile = Join-Path $claudeSettingsDir "settings.json"
if (Test-Path $gitBashExe) { if (-not (Test-Path $claudeSettingsDir)) { New-Item -Path $claudeSettingsDir -ItemType Directory -Force | Out-Null } $settings = @{} if (Test-Path $claudeSettingsFile) { try { $settings = Get-Content $claudeSettingsFile -Raw | ConvertFrom-Json -AsHashtable } catch { $settings = @{} } } if (-not $settings.ContainsKey('env')) { $settings['env'] = @{} } $settings['env']['CLAUDE_CODE_GIT_BASH_PATH'] = $gitBashExe $settings | ConvertTo-Json -Depth 10 | Set-Content $claudeSettingsFile -Encoding UTF8 Write-OK "Git Bash path configured in Claude settings" }
Git performance tweaks
if (Test-CmdExists 'git') { git config --global core.fscache true 2>$null git config --global core.preloadindex true 2>$null git config --global gc.auto 256 2>$null Write-OK "Git performance settings applied"
Check for git user config
$gitUser = git config --global user.name 2>$null $gitEmail = git config --global user.email 2>$null if (-not $gitUser -or -not $gitEmail) { Write-Warn "Git user.name or user.email not set. Git commits will fail without these." Write-Tip "Run: git config --global user.name 'Your Name'" Write-Tip "Run: git config --global user.email 'you@example.com'" } else { Write-OK "Git user: $gitUser <$gitEmail>" } git config --global init.defaultBranch main 2>$null } }
─── 3b. ChatGPT Terminal ─────────────────────────────────────────────────────
if ($installChatGPT) { Write-Section "Installing ChatGPT Terminal"
$chatGPTScript = Join-Path $ScriptsDir "Setup-ChatGPT-Terminal.ps1" if (Test-Path $chatGPTScript) { Write-Info "Found Setup-ChatGPT-Terminal.ps1" Write-Info "This sets up an interactive ChatGPT session right in PowerShell." Write-Info "Commands: chatgpt (interactive), cgpt 'question' (one-shot), gpt (alias)" Write-Host ""
Write-Step "Running ChatGPT Terminal setup..." try { & $chatGPTScript -InstallOnly Add-Result "ChatGPT Terminal" "INSTALLED" "Use: chatgpt, cgpt, gpt" } catch { Write-Warn "ChatGPT setup error: $" Add-Result "ChatGPT Terminal" "ERROR" "$" } } else { Write-Info "Setup-ChatGPT-Terminal.ps1 not found in $ScriptsDir" Write-Info "Setting up ChatGPT terminal manually..."
Check for OpenAI API key
if (-not $env:OPENAI_API_KEY) { Write-Host "" Write-Host " An OpenAI API key is needed. Get one at: https://platform.openai.com/api-keys" -ForegroundColor Yellow Write-Host " Enter your OpenAI API key (or press Enter to skip): " -NoNewline -ForegroundColor Yellow $openaiKey = Read-Host if ($openaiKey -and $openaiKey.StartsWith('sk-')) { [System.Environment]::SetEnvironmentVariable('OPENAI_API_KEY', $openaiKey, 'User') $env:OPENAI_API_KEY = $openaiKey Write-OK "OPENAI_API_KEY set" } } else { Write-OK "OPENAI_API_KEY already set" }
Install openai-chatgpt-cli if npm is available
if (Test-CmdExists 'npm') { Write-Info "Installing chatgpt CLI via npm..." try { npm install -g chatgpt-cli 2>&1 | Out-Null Refresh-Path Write-OK "chatgpt-cli installed" Add-Result "ChatGPT Terminal" "INSTALLED" "npm chatgpt-cli" } catch { Write-Warn "npm install failed. Use Setup-ChatGPT-Terminal.ps1 instead." Add-Result "ChatGPT Terminal" "PARTIAL" "Script-based only" } } else { Add-Result "ChatGPT Terminal" "SKIPPED" "Need npm or setup script" } } }
─── 3c. GitHub Copilot CLI ───────────────────────────────────────────────────
if ($installCopilot) { Write-Section "Installing GitHub Copilot CLI"
Refresh-Path $ghExists = Test-CmdExists 'gh'
if (-not $ghExists) { Write-Info "GitHub CLI (gh) is required for Copilot CLI." if (Get-YesNo "Install GitHub CLI?" $true) { if (Test-CmdExists 'winget') { winget install --id GitHub.cli --accept-source-agreements --accept-package-agreements --silent 2>$null } elseif (Test-CmdExists 'choco') { choco install gh --yes --no-progress 2>$null } else { Write-Info "Downloading from GitHub..." try { $ghRelease = Invoke-RestMethod 'https://api.github.com/repos/cli/cli/releases/latest' $ghAsset = $ghRelease.assets | Where-Object { $_.name -match 'windows_amd64.msi
Windows (PowerShell, as Administrator)
# One-time: allow the current session to run scripts
Set-ExecutionPolicy Bypass -Scope Process -Force
# Install Chocolatey (Windows package manager)
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
# Install runtimes + utilities
choco install -y git nodejs-lts python312 ripgrep jq fd 7zip imagemagick pwsh
# Verify
node --version ; npm --version ; python --version ; git --version
Linux (Bash, Ubuntu / Debian / Mint)
# Update + install runtimes + utilities
sudo apt update
sudo apt install -y git curl build-essential python3 python3-pip python3-venv ripgrep jq fd-find imagemagick
# Install Node.js LTS via NodeSource (the apt package is usually out of date)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version ; npm --version ; python3 --version ; git --version
Linux (Bash, Fedora / RHEL / Rocky)
sudo dnf install -y git curl python3 python3-pip ripgrep jq fd-find ImageMagick
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs
Close the terminal and open a new one after runtime installs. PATH updates only take effect in fresh sessions.
Step 2 — Install Claude Code
Anthropic's AI coding terminal. Reads your repo, edits files, runs commands, opens PRs.
Windows (PowerShell)
# Official installer (puts 'claude' on your PATH)
irm https://claude.ai/install.ps1 | iex
Linux (Bash)
curl -fsSL https://claude.ai/install.sh | sh
Both platforms verify:
claude --version
Authenticate once by just running claude in any directory, it opens a browser to claude.ai, you click Authorize, and you're done. No API key needed for the browser flow. If you prefer an API key (required for CI):
# Windows (persists across sessions):
[Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-your-key-here", "User")
# Linux:
echo 'export ANTHROPIC_API_KEY="sk-ant-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Get an API key at console.anthropic.com/settings/keys.
Step 3 — Install OpenAI Codex / ChatGPT CLI
The official OpenAI CLI for coding (Codex) and a quick ChatGPT terminal for one-shot questions.
Both platforms
# Official OpenAI Codex CLI (Node-based)
npm install -g @openai/codex
# Verify
codex --version
For a lightweight ChatGPT streaming terminal (python-based alternative):
pip install chatgpt-cli
# One-shot
chatgpt "explain rsync incremental backups in one paragraph"
# Interactive
chatgpt
Authenticate by setting your OpenAI key. Get one at platform.openai.com/api-keys (requires a paid account, $5 prepaid is enough).
# Windows
[Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-your-key-here", "User")
# Linux
echo 'export OPENAI_API_KEY="sk-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Step 4 — Install GitHub Copilot CLI
Copilot CLI lives inside the gh (GitHub) CLI as an extension.
Windows (PowerShell)
choco install -y gh
gh auth login # web browser flow — select GitHub.com + browser
gh extension install github/gh-copilot
Linux (Bash, Ubuntu / Debian / Mint)
# Add GitHub CLI apt repo
type -p curl >/dev/null || sudo apt install -y curl
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
gh auth login # choose GitHub.com → web browser → paste one-time code
gh extension install github/gh-copilot
Linux (Bash, Fedora / RHEL)
sudo dnf install -y 'dnf-command(config-manager)'
sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
sudo dnf install -y gh
gh auth login
gh extension install github/gh-copilot
Verify:
gh copilot suggest "find all PNG files larger than 1MB in current directory"
gh copilot explain "tar -xzf archive.tar.gz -C /opt/"
Copilot CLI requires a Copilot subscription ($10/month individual, free for students / verified OSS maintainers).
Step 5 — Install Netlify CLI
For deploying sites directly from the terminal after Claude / Codex writes them.
Both platforms
npm install -g netlify-cli
netlify --version
netlify login # browser opens → Authorize → back to terminal
Verify auth:
netlify status
Step 6 — Install Python AI SDKs (optional but useful)
For scripting against the APIs directly (no terminal UI).
# Both platforms — use pip (Linux may need python3 -m pip)
pip install anthropic openai google-generativeai fastmcp mcp requests httpx pandas
Step 7 — Your first session
From your project directory:
cd ~/my-website # or: cd $env:USERPROFILE\my-website on Windows
claude
Then at the Claude prompt:
> give me a summary of this project
> add a dark-mode toggle to the header, commit, and show me the diff
> deploy a draft to Netlify so I can preview it
Claude reads your files, edits them, runs commands, and reports back. When the draft looks right:
> deploy to production
One-shot mode (any of the three CLIs)
For quick answers without entering a session:
claude "explain what package.json does in this repo"
codex "write a bash script that rotates log files older than 14 days"
chatgpt "what's the difference between sync and async in Node.js?"
gh copilot suggest "rebase my feature branch on main without merge commits"
Common gotchas
command not foundafter install. Close and reopen your terminal. PATH changes only take effect in new sessions.Execution policyerror on Windows.Set-ExecutionPolicy Bypass -Scope Process -Forcethen re-run.Permission deniedon Linux scripts.chmod +x ./script.shthen rerun.- Netlify deploy shows wrong files. Specify the directory explicitly:
netlify deploy --dir=_site --prod. claudesays API key missing. Either runclaudewith no args (triggers browser OAuth) orecho $ANTHROPIC_API_KEYto confirm it's set.- OpenAI
insufficient_quotaerror. The API tier is separate from ChatGPT Plus, prepay $5 at platform.openai.com/account/billing. - Copilot CLI says "no subscription." A GitHub Pro / Team / Enterprise account doesn't include Copilot, you need the $10/mo Copilot plan or be in the free student/OSS program.
Cheat sheet — the commands you'll use daily
# Start AI sessions
claude # Claude Code interactive
claude "one-shot prompt" # Quick Claude query
codex # OpenAI Codex interactive
chatgpt # ChatGPT streaming terminal
gh copilot suggest "…" # Shell command suggestion
# Inside Claude Code
/help # slash commands
/clear # reset conversation
/mcp list # show connected services
Esc # exit
# Git (used constantly)
git status ; git diff ; git add . ; git commit -m "msg"
# Deploy
netlify deploy # preview URL
netlify deploy --prod # live
netlify deploy --dir=_site --prod # for SSG builds (Eleventy etc.)
Optional — connect Claude to Google Drive, Slack, GitHub via MCP
MCP (Model Context Protocol) servers let Claude read external apps from inside the terminal.
# Google Drive
claude mcp add gdrive -- npx @anthropic/gdrive-mcp
# Slack
claude mcp add slack -- npx @anthropic/slack-mcp
# GitHub (enhanced — reads issues, PRs, etc.)
claude mcp add github -- npx @anthropic/github-mcp
# Playwright (browser automation — screenshots, form-filling)
claude mcp add playwright -- npx @playwright/mcp@latest
# List / remove
claude mcp list
claude mcp remove gdrive
First run of each MCP prompts for OAuth. The tokens live locally; nothing is sent to Anthropic or OpenAI outside the normal API calls.
Why the terminal wins over the web UI
The 30-second argument: your code lives on the filesystem; your git history lives in the terminal; your builds run in the terminal; your deploys happen from the terminal. The web GUI makes you leave where the work happens, describe your problem in isolation, receive a response in isolation, then manually carry that response back to where the work happens. The terminal eliminates the round trip entirely.
On a 50-file refactor, the terminal approach takes 2 minutes. The GUI approach takes 45 minutes of copy-paste-switch-paste-switch-paste.
Side-by-side on one concrete task
Task: "Add a dark mode toggle to my website."
Web GUI (ChatGPT / Claude.ai):
- Open browser → claude.ai (8s)
- "add dark mode toggle" → AI asks for HTML → open file explorer, find
index.html, copy → paste in browser - AI asks for CSS → repeat for
styles.css - AI returns three code blocks → copy HTML → switch to editor → find insertion point → paste
- Copy CSS → find insertion point → paste
- Copy JS → create
toggle.js→ paste → add<script>tag to HTML - Refresh browser, hit a bug, copy the error, paste back, get fix, copy, switch, paste, test again
~20 steps, ~15 window switches, ~130 seconds of pure mechanical copy-paste.
Terminal (Claude Code):
cd ~/my-website && claude> add a dark mode toggle to the site- Press
yto approve the diff.
3 steps, 0 window switches, ~7 seconds of input. Time saved: ~2 minutes per change. Over a workday of 30 changes, that's an hour recovered.
What the terminal gives the model that the GUI can't
- Real filesystem access, every
.md,.njk,.toml,.envin your working directory, read on demand. No more copy-pasting individual files and forgetting one. - Command execution —
git status,npm run build,netlify deploy --prod,curlhealth checks, run and reacted to in the same loop, not copied out and run by you. - Multi-file atomic edits, rename a variable across 40 files, update imports, run tests, revert if anything breaks, one session. Browser UI is one file at a time and loses context each round-trip.
- Full git integration, commits with meaningful messages, branches, PR open, diff review, all native.
- No token bloat from pasted context, CLI streams file contents only when the model asks. Browser burns context on every copy-paste.
Speed, memory, battery
| Metric | Terminal | Web GUI |
|---|---|---|
| Startup | 1–2 s | 5–15 s |
| RAM | 50–150 MB | 300–800 MB per tab |
| Input latency | Instant | 50–100 ms (JS overhead) |
| File read | <1 ms (local) | Manual upload (2–30 s) |
| File write | <1 ms (local) | Copy-paste (10–30 s) |
| Battery hit (1 hr laptop) | ~2 % | ~8–15 % |
Over an 8-hour day of heavy AI use: terminal ~400 MB RAM, minimal battery hit. Browser ~3–5 GB RAM across tabs, significant battery drain.
Multi-file tasks the GUI cannot do
| Task | Terminal | Web GUI |
|---|---|---|
| Rename a function across 30 files | 15 s | 45 min |
| Add a header to 50 HTML pages | 20 s | 2 hrs |
| Update import paths across 100 files after a folder rename | 10 s | impossible |
Find & fix every console.log before deploy |
5 s | 30 min |
| Add error handling to every API call in the project | 30 s | 1 hr |
| Update copyright year in every page footer | 3 s | 20 min |
Web GUI fundamentally has no filesystem access, you'd paste files one at a time, get changes one at a time, apply them one at a time.
Security and audit
| Risk | Terminal | Web GUI |
|---|---|---|
| Browser extensions reading your code / API keys | Not possible | Major, extensions can read all page content |
| Clipboard exposure | Minimal (direct file writes) | Every copy-paste puts code on the clipboard |
| Session hijacking | API key in env var | Session cookie in browser (XSS surface) |
| Data in browser cache | None | Conversations cached in localStorage |
| Audit trail | Shell history (local file) | Proprietary, deletable |
API keys in the terminal are stored in environment variables, never displayed, OS-file-permission protected, invisible to browser extensions, and not sent to analytics or tracking services.
Where the terminal is the only option
- Remote servers & SSH, you can't open claude.ai on a headless server.
ssh prod && claudeis the whole workflow. Read nginx logs, fix config, restart service, one prompt. - Persistent sessions —
tmux new -s ai-session+claude, detach, close SSH, go home. Come back next morning,tmux attach -t ai-session, Claude is still there reporting what it found. - CI/CD —
claude --print "run the release checklist"in a GitHub Action, Vercel hook, or cron job. The browser cannot be scripted. - MCP connectors, Google Drive, Slack, GitHub, Playwright, databases. All terminal-only. The web GUI has zero MCP support.
When the GUI actually wins
- You want an image or voice conversation.
- You're on a Chromebook / iPad where you can't install tools.
- You want to share a conversation link.
- You're drag-dropping a PDF for one-off analysis.
- You're walking or driving and want voice interaction (mobile app).
That's ~10–20 % of professional AI coding work. The terminal handles the other 80–90 % faster, safer, and more reliably.
How Claude Code CLI + Netlify CLI work together for easy administration
Once both are installed and authenticated, the whole admin surface of a website collapses into prompts. Here's the pattern for the most common ops:
Deploy a change end-to-end
cd ~/my-website
claude
> add a privacy policy page that covers cookies and analytics, link it
from the footer, run the build, and deploy a draft to Netlify
Claude writes the page, updates the footer, runs npm run build (or npx @11ty/eleventy), runs netlify deploy, returns the preview URL. You check the preview in a browser, then:
> looks good, deploy to production and ping IndexNow
Add a redirect without touching config files
> add a 301 redirect from /old-page to /new-page in netlify.toml
and verify it works after deploy with curl
Claude edits netlify.toml, deploys, runs curl -I https://site.com/old-page to check for the Location: header. One prompt, proof-of-fix included.
Set environment variables securely
> set a Netlify environment variable SENDGRID_API_KEY to the value
I'll paste next, then redeploy
Claude uses netlify env:set SENDGRID_API_KEY ... (the value stays in the terminal session, never written to a file). Then it triggers a redeploy so the new env is picked up.
Investigate a production issue
> pull the last 3 Netlify deploy logs, summarize any errors, and
suggest a fix for each
netlify api listSiteDeploys returns the deploy history; Claude reads the logs, spots the pattern (build failure, timeout, bad env), and proposes the patch.
Migrate or clone a site
> this folder is a working Eleventy site. Create a new Netlify site
called "alpha-staging", link this folder to it, and do a prod deploy
Claude runs netlify sites:create --name alpha-staging, then netlify link --name alpha-staging, then netlify deploy --dir=_site --prod. Zero clicks in the Netlify dashboard.
Rollback fast
> the last deploy broke the homepage, restore the previous production
deploy
Claude uses netlify api listSiteDeploys to find the previous successful deploy ID, then netlify api restoreSiteDeploy to roll back. ~5 seconds.
The through-line: every Netlify admin action has a CLI equivalent, and every CLI equivalent is something Claude Code can run from a prompt. Once you accept that, the Netlify dashboard becomes a read-only status page and everything mutating happens via prompt.
Cross-promote: pair the terminal with the prompt generators on this site
The terminal gives Claude real filesystem + command access. The jwatte.com tool suite gives Claude the exact thing to do. That pairing is the whole productivity story.
Write better prompts before you hand them to Claude:
- Prompt Enhancer, wraps any prompt in research-backed patterns (ExpertPrompting, OPRO, EmotionPrompt, self-evaluation). Paste your rough instruction, pick an intensity, copy the enhanced version, hand it to
claudeorcodex. Companion post →
Generate ready-to-paste prompts from audit data:
- Mega Analyzer, audits one URL across SEO, schema, E-E-A-T, voice, mobile, performance, AI-search. The output ends in a multi-thousand-word AI fix prompt that names exact file paths and expected score deltas. Pipe that into
claudeand you go from "here's what's broken" to "here's the fix, committed and deployed" in one session. - Site Analyzer, same pattern, 70+ checks, different scoring buckets. Also emits a copy-paste AI fix prompt.
- Batch Compare, up to 10 URLs compared side-by-side with a combined AI prompt covering the whole portfolio. Feed it to Claude and fix 10 sites from one session.
- Link Graph, crawls a site, finds orphans / hubs / dead-ends / noindex pages, and emits an AI fix prompt that proposes exact internal-link additions (source page + destination + anchor text). Paste, tell Claude "apply every recommendation, commit grouped by destination page, deploy", done.
Scaffold new sites from a prompt:
- Single Site Gen, emits a full AI site-build prompt with every best practice (schema, llms.txt, IndexNow, security headers, E-E-A-T signals, WCAG 2.2) baked in.
claude < singlesitegen-prompt.txtand Claude scaffolds the whole thing in~/new-site/. - Monoclone Generator, for when you're spinning up an industry site from a template. Generates the deploy prompt for the whole network.
Generate JSON-LD / schema blocks Claude can drop in directly:
- E-E-A-T Generator, Person / Organization / sameAs / Wikidata / ORCID / rel=me JSON-LD from a single author profile.
- Speakable Generator, SpeakableSpecification JSON-LD for voice + AI-citation.
- FAQ Harvester, pulls every FAQ from the Google top 10, dedupes, emits ready-to-paste FAQPage JSON-LD.
- ItemList / Carousel, Google-compliant ItemList JSON-LD from a URL list.
Audit without leaving the terminal:
- .well-known Audit, 13-file audit of
/.well-known/with copy-paste fix kit. - ai.txt Generator, Spawning-style AI training opt-in/out policy across every bot in the shared registry (robots.txt companion, Netlify/Apache deploy config).
Concrete workflow that ties all of it together:
# 1. Browser: run /tools/mega-analyzer/ on your site, click "Copy AI fix prompt"
# 2. Terminal:
cd ~/my-site
claude
> [paste the Mega Analyzer fix prompt]
# Claude works through every check, edits the files, commits
> run a mobile parity check, then deploy to Netlify prod if pass
# Claude runs /tools/mobile-parity/ mentally, or you paste its URL as input
The tools produce prompts; the terminal consumes prompts. The browser is just the scratchpad where you collect the prompt.
When to reach for which tool
- Editing a real project, multi-file changes, deploys. → Claude Code. It's the only one that reads + writes files end-to-end in your working directory.
- Quick one-off code snippet or explanation. → Codex or
chatgptone-shot mode. Faster, cheaper, no project context needed. - "What's the shell command for X?" → Copilot CLI. Specifically trained for the one-line shell suggestion task.
- GUI preference / on a tablet or Chromebook. → claude.ai/code (browser-based Claude Code), chatgpt.com, VS Code Copilot extension.
The terminal versions are faster for anything that involves your local repo. The GUI versions are better for pure conversation or when you can't install locally.
Related reading
Next up — the follow-on walkthrough: You've Got The Claude Code CLI Installed — Now What? picks up where this post leaves off. Plan mode, CLAUDE.md as a living contract, the slash commands that matter (/btw /fork /rewind /compact /loop /schedule /simplify /batch /powerup /insights /debug /claude-api), Skills vs Rules vs Memory, Auto Mode boundaries, and a Day 0 → Day 7 ramp plan.
Companion coverage from the methodology stack:
- The $97 Launch, Chapter 1 (domain + hosting + deploy). Claude Code + Netlify CLI is the end-state of the workflow that chapter sets up.
- The $20 Dollar Agency, Chapters 5-11 (SEO, schema, keywords). Every audit pattern the Site Analyzer and Mega Analyzer run is something Claude Code can implement in one prompt once the environment is live.
- The $100 Network, Chapter 6 (the provider stack) + Chapter 26 (monitoring at scale). Where the AI terminal fits when you're running more than one site.
} | Select-Object -First 1
if ($ghAsset) {
$ghMsi = Join-Path $script:TempDir $ghAsset.name
Invoke-WebRequest -Uri $ghAsset.browser_download_url -OutFile $ghMsi -UseBasicParsing
Start-Process msiexec.exe -ArgumentList "/i
"$ghMsi" /qn /norestart" -Wait -NoNewWindow } } catch { Write-Fail "GitHub CLI download failed: $_" } } Refresh-Path } }
if (Test-CmdExists 'gh') { Write-OK "GitHub CLI installed: $(gh --version 2>$null | Select-Object -First 1)"
Install Copilot extension
Write-Step "Installing GitHub Copilot CLI extension..." try { gh extension install github/gh-copilot 2>&1 | Out-Host Write-OK "Copilot CLI extension installed" Write-Info "" Write-Info "Usage examples:" Write-Info " gh copilot suggest 'find large files over 100MB'" Write-Info " gh copilot explain 'git rebase -i HEAD~3'" Write-Info "" Write-Tip "You need a GitHub Copilot subscription. Log in with: gh auth login" Add-Result "GitHub Copilot CLI" "INSTALLED" "gh copilot suggest/explain" } catch { Write-Warn "Copilot extension install error: $_" Write-Tip "You may need to log in first: gh auth login" Add-Result "GitHub Copilot CLI" "PARTIAL" "Login may be needed" } } else { Write-Fail "GitHub CLI not available - Copilot CLI cannot be installed" Add-Result "GitHub Copilot CLI" "FAILED" "gh CLI missing" } }
═══════════════════════════════════════════════════════════════════════════════
PHASE 3d - NETLIFY CLI & DEPLOYMENT
═══════════════════════════════════════════════════════════════════════════════
Enter-Phase "Deployment & Extras" Write-Section "Netlify CLI & Deployment Tools"
Write-Step "Checking Netlify CLI" Refresh-Path if (Test-CmdExists 'netlify') { $netlifyVer = netlify --version 2>$null Write-OK "Netlify CLI already installed ($netlifyVer)" Add-Result "Netlify CLI" "PRESENT" "$netlifyVer" } else { Write-Info "Netlify CLI lets you deploy websites directly from your terminal." Write-Info "Free tier: 100GB bandwidth, 300 build minutes/month, custom domains." Write-Info "Works beautifully with Claude Code - AI edits your site, then deploys." if (Get-YesNo "Install Netlify CLI?" $true) { if (Test-CmdExists 'npm') { Write-Info "Installing netlify-cli globally via npm..." try { npm install -g netlify-cli 2>&1 | Out-Host Refresh-Path if (Test-CmdExists 'netlify') { Write-OK "Netlify CLI installed ($(netlify --version 2>$null))" Add-Result "Netlify CLI" "INSTALLED" "" } else { Write-Warn "Netlify CLI installed but restart terminal for PATH" Add-Result "Netlify CLI" "INSTALLED" "Restart terminal" } } catch { Write-Fail "Netlify CLI install failed: $" Add-Result "Netlify CLI" "FAILED" "$" } } else { Write-Fail "npm not available - install Node.js first" Add-Result "Netlify CLI" "FAILED" "npm missing" } } else { Add-Result "Netlify CLI" "SKIPPED" "" } }
Netlify authentication guide
Write-Host "" Write-Host " ┌──────────────────────────────────────────────────────────────┐" -ForegroundColor DarkCyan Write-Host " │ NETLIFY AUTHENTICATION & DEPLOYMENT │" -ForegroundColor DarkCyan Write-Host " ├──────────────────────────────────────────────────────────────┤" -ForegroundColor DarkCyan Write-Host " │ │" -ForegroundColor DarkCyan Write-Host " │ LOGIN: │" -ForegroundColor DarkCyan Write-Host " │ netlify login │" -ForegroundColor White Write-Host " │ (browser opens > click Authorize > return to terminal) │" -ForegroundColor Gray Write-Host " │ │" -ForegroundColor DarkCyan Write-Host " │ DEPLOY FROM ANY AI TERMINAL: │" -ForegroundColor DarkCyan Write-Host " │ cd C:\path\to\my-website │" -ForegroundColor White Write-Host " │ netlify init # link folder to Netlify site │" -ForegroundColor White Write-Host " │ netlify deploy # preview draft │" -ForegroundColor White Write-Host " │ netlify deploy --prod # push live │" -ForegroundColor White Write-Host " │ │" -ForegroundColor DarkCyan Write-Host " │ AI + DEPLOY (Claude Code): │" -ForegroundColor DarkCyan Write-Host " │ cd C:\my-site && claude │" -ForegroundColor White Write-Host " │ > fix the nav menu and deploy a draft to Netlify │" -ForegroundColor White Write-Host " │ │" -ForegroundColor DarkCyan Write-Host " │ AI + DEPLOY (ChatGPT > manual): │" -ForegroundColor DarkCyan Write-Host " │ gpt │" -ForegroundColor White Write-Host " │ You > write me the CSS for a responsive hero section │" -ForegroundColor White Write-Host " │ (copy output to your file, then: netlify deploy --prod) │" -ForegroundColor Gray Write-Host " │ │" -ForegroundColor DarkCyan Write-Host " │ AI + DEPLOY (Copilot CLI): │" -ForegroundColor DarkCyan Write-Host " │ gh copilot suggest 'deploy this folder to Netlify' │" -ForegroundColor White Write-Host " └──────────────────────────────────────────────────────────────┘" -ForegroundColor DarkCyan
if (Test-CmdExists 'netlify') { Write-Host "" if (Get-YesNo "Log in to Netlify now?" $false) { Write-Step "Opening Netlify login..." netlify login } }
─── Additional: Windows Terminal & Fonts ──────────────────────────────────────
Write-Step "Checking Windows Terminal" $wtInstalled = Get-AppxPackage -Name "Microsoft.WindowsTerminal" -ErrorAction SilentlyContinue if ($wtInstalled) { Write-OK "Windows Terminal installed" Add-Result "Windows Terminal" "PRESENT" "" } else { Write-Info "Windows Terminal provides tabs, GPU rendering, and Nerd Font support." if (Get-YesNo "Install Windows Terminal?") { if (Test-CmdExists 'winget') { winget install --id Microsoft.WindowsTerminal --accept-source-agreements --accept-package-agreements --silent 2>$null Write-OK "Windows Terminal installed" Add-Result "Windows Terminal" "INSTALLED" "" } else { Write-Info "Install from Microsoft Store: search 'Windows Terminal'" Add-Result "Windows Terminal" "SKIPPED" "Install from Store" } } else { Add-Result "Windows Terminal" "SKIPPED" "" } }
UTF-8 console
Write-Step "Setting console to UTF-8" $currentCP = chcp 2>$null if ($currentCP -notmatch '65001') { chcp 65001 > $null 2>&1 Write-OK "Console code page set to UTF-8 (65001)" } else { Write-OK "Console already UTF-8" }
Vercel CLI
Write-Step "Checking Vercel CLI" Refresh-Path if (Test-CmdExists 'vercel') { Write-OK "Vercel CLI already installed" Add-Result "Vercel CLI" "PRESENT" "" } else { Write-Info "Vercel is another popular hosting platform (like Netlify)." if (Get-YesNo "Install Vercel CLI?" $false) { if (Test-CmdExists 'npm') { npm install -g vercel 2>&1 | Out-Null Refresh-Path Write-OK "Vercel CLI installed" Add-Result "Vercel CLI" "INSTALLED" "" } } else { Add-Result "Vercel CLI" "SKIPPED" "" } }
═══════════════════════════════════════════════════════════════════════════════
PHASE 4 - VALIDATION & HEALTH CHECK
═══════════════════════════════════════════════════════════════════════════════
Enter-Phase "Validation & Health Check" Write-Section "PHASE 4: Environment Health Check"
Refresh-Path
$checks = @( @{ Name = "PowerShell"; Cmd = "pwsh --version"; Alt = "`$PSVersionTable.PSVersion" }, @{ Name = "Git"; Cmd = "git --version"; Alt = $null }, @{ Name = "Git Bash"; Cmd = $null; Path = "$env:ProgramFiles\Git\bin\bash.exe" }, @{ Name = "Node.js"; Cmd = "node --version"; Alt = $null }, @{ Name = "npm"; Cmd = "npm --version"; Alt = $null }, @{ Name = "npx"; Cmd = "npx --version"; Alt = $null }, @{ Name = "Python"; Cmd = "python --version"; Alt = $null }, @{ Name = "pip"; Cmd = "pip --version"; Alt = $null }, @{ Name = "uv"; Cmd = "uv --version"; Alt = $null }, @{ Name = "Claude Code"; Cmd = "claude --version"; Alt = $null; Path2 = "$env:USERPROFILE.local\bin\claude.exe" }, @{ Name = "GitHub CLI"; Cmd = "gh --version"; Alt = $null }, @{ Name = "ripgrep"; Cmd = "rg --version"; Alt = $null }, @{ Name = "jq"; Cmd = "jq --version"; Alt = $null }, @{ Name = "ImageMagick"; Cmd = "magick --version"; Alt = $null }, @{ Name = "GhostScript"; Cmd = "gswin64c --version"; Alt = $null }, @{ Name = "FFmpeg"; Cmd = "ffmpeg -version"; Alt = $null }, @{ Name = "bat"; Cmd = "bat --version"; Alt = $null }, @{ Name = "fzf"; Cmd = "fzf --version"; Alt = $null }, @{ Name = "delta"; Cmd = "delta --version"; Alt = $null }, @{ Name = "shellcheck"; Cmd = "shellcheck --version"; Alt = $null }, @{ Name = "yq"; Cmd = "yq --version"; Alt = $null }, @{ Name = "Netlify CLI"; Cmd = "netlify --version"; Alt = $null }, @{ Name = "Vercel CLI"; Cmd = "vercel --version"; Alt = $null }, @{ Name = "Chocolatey"; Cmd = "choco --version"; Alt = $null } )
─── Functional Validation (not just version checks) ──────────────────────────
Write-Step "Running functional validation tests"
$funcTests = @()
Test Node.js can actually execute JavaScript
if (Test-CmdExists 'node') { try { $nodeTest = node -e "console.log('node-ok')" 2>&1 if ($nodeTest -match 'node-ok') { $funcTests += @{ Name = "Node.js exec"; Pass = $true } } else { $funcTests += @{ Name = "Node.js exec"; Pass = $false } } } catch { $funcTests += @{ Name = "Node.js exec"; Pass = $false } } }
Test Python can import key AI packages
if (Test-CmdExists 'python') { try { $pyTest = python -c "import ssl; print('py-ssl-ok:', ssl.OPENSSL_VERSION)" 2>&1 if ($pyTest -match 'py-ssl-ok') { $funcTests += @{ Name = "Python SSL"; Pass = $true } } else { $funcTests += @{ Name = "Python SSL"; Pass = $false } } } catch { $funcTests += @{ Name = "Python SSL"; Pass = $false } }
try { $pyImport = python -c "import requests, pydantic; print('imports-ok')" 2>&1 if ($pyImport -match 'imports-ok') { $funcTests += @{ Name = "Python packages"; Pass = $true } } else { $funcTests += @{ Name = "Python packages"; Pass = $false } } } catch { $funcTests += @{ Name = "Python packages"; Pass = $false } } }
Test npm registry connectivity
if (Test-CmdExists 'npm') { try { $npmPing = npm ping 2>&1 if ($LASTEXITCODE -eq 0) { $funcTests += @{ Name = "npm registry"; Pass = $true } } else { $funcTests += @{ Name = "npm registry"; Pass = $false } } } catch { $funcTests += @{ Name = "npm registry"; Pass = $false } } }
Test Git can actually connect (credential check)
if (Test-CmdExists 'git') { try { $gitLs = git ls-remote --heads https://github.com/anthropics/claude-code.git 2>&1 if ($LASTEXITCODE -eq 0) { $funcTests += @{ Name = "Git HTTPS"; Pass = $true } } else { $funcTests += @{ Name = "Git HTTPS"; Pass = $false } } } catch { $funcTests += @{ Name = "Git HTTPS"; Pass = $false } } }
Test ImageMagick delegates (GhostScript for PDF)
if (Test-CmdExists 'magick') { try { $delegates = magick identify -list format 2>&1 if ($delegates -match 'PDF') { $funcTests += @{ Name = "ImageMagick PDF"; Pass = $true } } else { $funcTests += @{ Name = "ImageMagick PDF"; Pass = $false } } } catch { $funcTests += @{ Name = "ImageMagick PDF"; Pass = $false } } }
foreach ($ft in $funcTests) { if ($ft.Pass) { Write-Host " $($ft.Name.PadRight(18)) " -NoNewline Write-Host "PASS" -ForegroundColor Green } else { Write-Host " $($ft.Name.PadRight(18)) " -NoNewline Write-Host "FAIL" -ForegroundColor Red } }
Write-Host "" $colName = "Component".PadRight(18) $colStatus = "Status".PadRight(10) $colVer = "Version / Details" Write-Host " $colName $colStatus $colVer" -ForegroundColor White Write-Host " $('-' * 18) $('-' * 10) $('-' * 35)" -ForegroundColor DarkGray
$passCount = 0 $failCount = 0
foreach ($check in $checks) { $name = $check.Name.PadRight(18) $ver = "" $found = $false
Check via command
if ($check.Cmd) { try { $ver = Invoke-Expression $check.Cmd 2>&1 | Select-Object -First 1 if ($LASTEXITCODE -eq 0 -or $ver) { $found = $true } } catch {} }
Check via path
if (-not $found -and $check.Path) { if (Test-Path $check.Path) { $found = $true $ver = $check.Path } } if (-not $found -and $check.Path2) { if (Test-Path $check.Path2) { $found = $true $ver = $check.Path2 } }
Check via alt expression
if (-not $found -and $check.Alt) { try { $ver = Invoke-Expression $check.Alt 2>&1 if ($ver) { $found = $true } } catch {} }
if ($found) { $status = "PASS".PadRight(10) $verStr = "$ver".Trim() if ($verStr.Length -gt 35) { $verStr = $verStr.Substring(0, 35) } Write-Host " $name " -NoNewline Write-Host $status -NoNewline -ForegroundColor Green Write-Host $verStr -ForegroundColor Gray $passCount++ } else { $status = "MISSING".PadRight(10) Write-Host " $name " -NoNewline Write-Host $status -NoNewline -ForegroundColor Red Write-Host "Not installed" -ForegroundColor DarkGray $failCount++ } }
Write-Host "" Write-Host " $('-' * 65)" -ForegroundColor DarkGray Write-Host " Total: $passCount passed, $failCount missing" -ForegroundColor $(if ($failCount -eq 0) { 'Green' } else { 'Yellow' })
═══════════════════════════════════════════════════════════════════════════════
PHASE 5 - QUICK REFERENCE GUIDE
═══════════════════════════════════════════════════════════════════════════════
Complete-Progress "AI Terminal Kickstart" Write-Section "PHASE 5: Quick Reference Guide"
$elapsed = (Get-Date) - $script:StartTime
Write-Host @"
┌─────────────────────────────────────────────────────────────────┐ │ HOW TO USE YOUR AI TERMINALS │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ CLAUDE CODE (Best for coding, files, agents) │ │ ───────────────────────────────────────────── │ │ claude Start interactive session │ │ claude "fix this bug" One-shot command │ │ claude --help See all options │ │ claude doctor Diagnose configuration │ │ claude mcp list Show MCP servers │ │ /help In-session help │ │ │ │ First-time auth: run 'claude' > browser opens > log in at │ │ claude.ai > click Authorize > return to terminal > done! │ │ │ │ Keyboard: Enter = send | Ctrl+C = cancel | Escape = exit │ │ Power moves: Ask it to edit files, run tests, create PRs │ │ │ │ CHATGPT TERMINAL (Best for quick Q&A, brainstorming) │ │ ───────────────────────────────────────────────── │ │ chatgpt Start interactive chat │ │ gpt Alias for chatgpt │ │ cgpt "explain X" Quick one-shot question │ │ /model gpt-4o Switch models mid-chat │ │ /save Export session to JSON │ │ /clear Reset conversation │ │ │ │ GITHUB COPILOT CLI (Best for shell commands) │ │ ───────────────────────────────────────────── │ │ gh copilot suggest "..." Get command suggestions │ │ gh copilot explain "..." Explain a command │ │ gh auth login Log in to GitHub first │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ NETLIFY DEPLOYMENT (direct deploy from any AI terminal) │ │ ────────────────────────────────────────────────── │ │ netlify login Authenticate (one-time) │ │ cd C:\path\to\site Go to your site folder │ │ netlify init Link to Netlify site (one-time) │ │ netlify deploy Preview deploy (draft URL) │ │ netlify deploy --prod Push to production (live URL) │ │ netlify open Open live site in browser │ │ netlify env:set KEY val Set environment variables │ │ │ │ AI + DEPLOY WORKFLOWS: │ │ Claude: cd site && claude │ │ > fix the footer and deploy a draft to Netlify │ │ ChatGPT: gpt > ask for code > paste into file > netlify deploy│ │ Copilot: gh copilot suggest "deploy to Netlify" │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ GUI & WEB INTERFACES │ │ ──────────────────── │ │ Claude: https://claude.ai/code (web app) │ │ VS Code extension: "Claude Code" in marketplace │ │ JetBrains extension: search "Claude" in plugins │ │ ChatGPT: https://chatgpt.com (web interface) │ │ Desktop app: https://openai.com/chatgpt/download │ │ Copilot: Built into VS Code (GitHub Copilot extension) │ │ Built into github.com (Copilot Chat in repos) │ │ Netlify: https://app.netlify.com (dashboard) │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ GOOGLE DRIVE & APP CONNECTORS (via MCP Servers) │ │ ─────────────────────────────────────────────── │ │ Claude Code can connect to external apps via MCP: │ │ │ │ Google Drive: │ │ claude mcp add gdrive -- npx @anthropic/gdrive-mcp │ │ (requires Google OAuth - follow the prompts) │ │ Then: > list my Google Drive files │ │ > read the doc called "Project Plan" │ │ │ │ Slack: │ │ claude mcp add slack -- npx @anthropic/slack-mcp │ │ Then: > check #general for recent messages │ │ │ │ GitHub: │ │ claude mcp add github -- npx @anthropic/github-mcp │ │ Then: > list open PRs in my repo │ │ │ │ Filesystem (already built-in): │ │ Claude Code reads/writes files in your current directory │ │ by default - no extra setup needed. │ │ │ │ Playwright (browser automation): │ │ claude mcp add playwright -- npx @playwright/mcp@latest │ │ Then: > take a screenshot of my site at localhost:8080 │ │ │ │ COMMON TIPS FOR ALL TERMINALS │ │ ───────────────────────────── │ │ - Be specific in your prompts for better results │ │ - Use quotes around multi-word arguments │ │ - Start simple, then add detail if output is wrong │ │ - Check 'claude doctor' or '/help' if something breaks │ │ - Restart terminal after fresh installs for PATH updates │ │ │ └─────────────────────────────────────────────────────────────────┘
"@ -ForegroundColor Cyan
═══════════════════════════════════════════════════════════════════════════════
FINAL SUMMARY
═══════════════════════════════════════════════════════════════════════════════
Write-Host "" Write-Host " ╔═══════════════════════════════════════════════════════════════════╗" -ForegroundColor Green Write-Host " ║ KICKSTART COMPLETE ║" -ForegroundColor Green Write-Host " ╠═══════════════════════════════════════════════════════════════════╣" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green
Display results table
$colComp = "Component".PadRight(22) $colStat = "Status".PadRight(12) $colDetail = "Detail" Write-Host " ║ $colComp $colStat $colDetail" -ForegroundColor Green Write-Host " ║ $('-' * 22) $('-' * 12) $('-' * 25)" -ForegroundColor DarkGreen
foreach ($r in $script:Results) { $comp = $r.Component.PadRight(22) $stat = $r.Status.PadRight(12) $det = if ($r.Detail.Length -gt 25) { $r.Detail.Substring(0, 25) } else { $r.Detail } $color = switch -Regex ($r.Status) { 'INSTALL|PRESENT|RAN|OK' { 'Green' } 'SKIPPED|PARTIAL' { 'Yellow' } 'FAILED|ERROR' { 'Red' } default { 'Gray' } } Write-Host " ║ " -NoNewline -ForegroundColor Green Write-Host "$comp " -NoNewline -ForegroundColor $color Write-Host "$stat " -NoNewline -ForegroundColor $color Write-Host "$det" -ForegroundColor Gray }
Write-Host " ║ ║" -ForegroundColor Green Write-Host " ║ Time elapsed: $($elapsed.ToString('mm:ss')) ║" -ForegroundColor Green Write-Host " ║ Log file: $($script:LogFile.PadRight(45)) ║" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green Write-Host " ║ NEXT STEPS: ║" -ForegroundColor Green Write-Host " ║ 1. CLOSE this terminal and open a NEW one (PATH refresh) ║" -ForegroundColor Green Write-Host " ║ 2. Run 'claude' to start Claude Code ║" -ForegroundColor Green Write-Host " ║ 3. Run 'chatgpt' or 'gpt' for ChatGPT ║" -ForegroundColor Green Write-Host " ║ 4. Run 'gh auth login' then 'gh copilot suggest...' ║" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green Write-Host " ╚═══════════════════════════════════════════════════════════════════╝" -ForegroundColor Green Write-Host ""
Cleanup temp if empty
if ((Get-ChildItem $script:TempDir -File -ErrorAction SilentlyContinue).Count -le 1) {
Keep the log file, remove installers
Get-ChildItem $script:TempDir -File -Exclude "kickstart.log" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue }
Write-Host " Press any key to exit..." -ForegroundColor Gray try { $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } catch {}
</details>
Both scripts are logged to a temp file (`/tmp/ai-kickstart-*/kickstart.log` on Linux, `%TEMP%\ai-kickstart-*\kickstart.log` on Windows) so you can review exactly what happened if something didn't install the first time.
## Step 1 — Install the runtimes
### Windows (PowerShell, as Administrator)
```powershell
# One-time: allow the current session to run scripts
Set-ExecutionPolicy Bypass -Scope Process -Force
# Install Chocolatey (Windows package manager)
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
# Install runtimes + utilities
choco install -y git nodejs-lts python312 ripgrep jq fd 7zip imagemagick pwsh
# Verify
node --version ; npm --version ; python --version ; git --version
Linux (Bash, Ubuntu / Debian / Mint)
# Update + install runtimes + utilities
sudo apt update
sudo apt install -y git curl build-essential python3 python3-pip python3-venv ripgrep jq fd-find imagemagick
# Install Node.js LTS via NodeSource (the apt package is usually out of date)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version ; npm --version ; python3 --version ; git --version
Linux (Bash, Fedora / RHEL / Rocky)
sudo dnf install -y git curl python3 python3-pip ripgrep jq fd-find ImageMagick
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs
Close the terminal and open a new one after runtime installs. PATH updates only take effect in fresh sessions.
Step 2 — Install Claude Code
Anthropic's AI coding terminal. Reads your repo, edits files, runs commands, opens PRs.
Windows (PowerShell)
# Official installer (puts 'claude' on your PATH)
irm https://claude.ai/install.ps1 | iex
Linux (Bash)
curl -fsSL https://claude.ai/install.sh | sh
Both platforms verify:
claude --version
Authenticate once by just running claude in any directory, it opens a browser to claude.ai, you click Authorize, and you're done. No API key needed for the browser flow. If you prefer an API key (required for CI):
# Windows (persists across sessions):
[Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-your-key-here", "User")
# Linux:
echo 'export ANTHROPIC_API_KEY="sk-ant-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Get an API key at console.anthropic.com/settings/keys.
Step 3 — Install OpenAI Codex / ChatGPT CLI
The official OpenAI CLI for coding (Codex) and a quick ChatGPT terminal for one-shot questions.
Both platforms
# Official OpenAI Codex CLI (Node-based)
npm install -g @openai/codex
# Verify
codex --version
For a lightweight ChatGPT streaming terminal (python-based alternative):
pip install chatgpt-cli
# One-shot
chatgpt "explain rsync incremental backups in one paragraph"
# Interactive
chatgpt
Authenticate by setting your OpenAI key. Get one at platform.openai.com/api-keys (requires a paid account, $5 prepaid is enough).
# Windows
[Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-your-key-here", "User")
# Linux
echo 'export OPENAI_API_KEY="sk-your-key-here"' >> ~/.bashrc
source ~/.bashrc
Step 4 — Install GitHub Copilot CLI
Copilot CLI lives inside the gh (GitHub) CLI as an extension.
Windows (PowerShell)
choco install -y gh
gh auth login # web browser flow — select GitHub.com + browser
gh extension install github/gh-copilot
Linux (Bash, Ubuntu / Debian / Mint)
# Add GitHub CLI apt repo
type -p curl >/dev/null || sudo apt install -y curl
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
gh auth login # choose GitHub.com → web browser → paste one-time code
gh extension install github/gh-copilot
Linux (Bash, Fedora / RHEL)
sudo dnf install -y 'dnf-command(config-manager)'
sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
sudo dnf install -y gh
gh auth login
gh extension install github/gh-copilot
Verify:
gh copilot suggest "find all PNG files larger than 1MB in current directory"
gh copilot explain "tar -xzf archive.tar.gz -C /opt/"
Copilot CLI requires a Copilot subscription ($10/month individual, free for students / verified OSS maintainers).
Step 5 — Install Netlify CLI
For deploying sites directly from the terminal after Claude / Codex writes them.
Both platforms
npm install -g netlify-cli
netlify --version
netlify login # browser opens → Authorize → back to terminal
Verify auth:
netlify status
Step 6 — Install Python AI SDKs (optional but useful)
For scripting against the APIs directly (no terminal UI).
# Both platforms — use pip (Linux may need python3 -m pip)
pip install anthropic openai google-generativeai fastmcp mcp requests httpx pandas
Step 7 — Your first session
From your project directory:
cd ~/my-website # or: cd $env:USERPROFILE\my-website on Windows
claude
Then at the Claude prompt:
> give me a summary of this project
> add a dark-mode toggle to the header, commit, and show me the diff
> deploy a draft to Netlify so I can preview it
Claude reads your files, edits them, runs commands, and reports back. When the draft looks right:
> deploy to production
One-shot mode (any of the three CLIs)
For quick answers without entering a session:
claude "explain what package.json does in this repo"
codex "write a bash script that rotates log files older than 14 days"
chatgpt "what's the difference between sync and async in Node.js?"
gh copilot suggest "rebase my feature branch on main without merge commits"
Common gotchas
command not foundafter install. Close and reopen your terminal. PATH changes only take effect in new sessions.Execution policyerror on Windows.Set-ExecutionPolicy Bypass -Scope Process -Forcethen re-run.Permission deniedon Linux scripts.chmod +x ./script.shthen rerun.- Netlify deploy shows wrong files. Specify the directory explicitly:
netlify deploy --dir=_site --prod. claudesays API key missing. Either runclaudewith no args (triggers browser OAuth) orecho $ANTHROPIC_API_KEYto confirm it's set.- OpenAI
insufficient_quotaerror. The API tier is separate from ChatGPT Plus, prepay $5 at platform.openai.com/account/billing. - Copilot CLI says "no subscription." A GitHub Pro / Team / Enterprise account doesn't include Copilot, you need the $10/mo Copilot plan or be in the free student/OSS program.
Cheat sheet — the commands you'll use daily
# Start AI sessions
claude # Claude Code interactive
claude "one-shot prompt" # Quick Claude query
codex # OpenAI Codex interactive
chatgpt # ChatGPT streaming terminal
gh copilot suggest "…" # Shell command suggestion
# Inside Claude Code
/help # slash commands
/clear # reset conversation
/mcp list # show connected services
Esc # exit
# Git (used constantly)
git status ; git diff ; git add . ; git commit -m "msg"
# Deploy
netlify deploy # preview URL
netlify deploy --prod # live
netlify deploy --dir=_site --prod # for SSG builds (Eleventy etc.)
Optional — connect Claude to Google Drive, Slack, GitHub via MCP
MCP (Model Context Protocol) servers let Claude read external apps from inside the terminal.
# Google Drive
claude mcp add gdrive -- npx @anthropic/gdrive-mcp
# Slack
claude mcp add slack -- npx @anthropic/slack-mcp
# GitHub (enhanced — reads issues, PRs, etc.)
claude mcp add github -- npx @anthropic/github-mcp
# Playwright (browser automation — screenshots, form-filling)
claude mcp add playwright -- npx @playwright/mcp@latest
# List / remove
claude mcp list
claude mcp remove gdrive
First run of each MCP prompts for OAuth. The tokens live locally; nothing is sent to Anthropic or OpenAI outside the normal API calls.
Why the terminal wins over the web UI
The 30-second argument: your code lives on the filesystem; your git history lives in the terminal; your builds run in the terminal; your deploys happen from the terminal. The web GUI makes you leave where the work happens, describe your problem in isolation, receive a response in isolation, then manually carry that response back to where the work happens. The terminal eliminates the round trip entirely.
On a 50-file refactor, the terminal approach takes 2 minutes. The GUI approach takes 45 minutes of copy-paste-switch-paste-switch-paste.
Side-by-side on one concrete task
Task: "Add a dark mode toggle to my website."
Web GUI (ChatGPT / Claude.ai):
- Open browser → claude.ai (8s)
- "add dark mode toggle" → AI asks for HTML → open file explorer, find
index.html, copy → paste in browser - AI asks for CSS → repeat for
styles.css - AI returns three code blocks → copy HTML → switch to editor → find insertion point → paste
- Copy CSS → find insertion point → paste
- Copy JS → create
toggle.js→ paste → add<script>tag to HTML - Refresh browser, hit a bug, copy the error, paste back, get fix, copy, switch, paste, test again
~20 steps, ~15 window switches, ~130 seconds of pure mechanical copy-paste.
Terminal (Claude Code):
cd ~/my-website && claude> add a dark mode toggle to the site- Press
yto approve the diff.
3 steps, 0 window switches, ~7 seconds of input. Time saved: ~2 minutes per change. Over a workday of 30 changes, that's an hour recovered.
What the terminal gives the model that the GUI can't
- Real filesystem access, every
.md,.njk,.toml,.envin your working directory, read on demand. No more copy-pasting individual files and forgetting one. - Command execution —
git status,npm run build,netlify deploy --prod,curlhealth checks, run and reacted to in the same loop, not copied out and run by you. - Multi-file atomic edits, rename a variable across 40 files, update imports, run tests, revert if anything breaks, one session. Browser UI is one file at a time and loses context each round-trip.
- Full git integration, commits with meaningful messages, branches, PR open, diff review, all native.
- No token bloat from pasted context, CLI streams file contents only when the model asks. Browser burns context on every copy-paste.
Speed, memory, battery
| Metric | Terminal | Web GUI |
|---|---|---|
| Startup | 1–2 s | 5–15 s |
| RAM | 50–150 MB | 300–800 MB per tab |
| Input latency | Instant | 50–100 ms (JS overhead) |
| File read | <1 ms (local) | Manual upload (2–30 s) |
| File write | <1 ms (local) | Copy-paste (10–30 s) |
| Battery hit (1 hr laptop) | ~2 % | ~8–15 % |
Over an 8-hour day of heavy AI use: terminal ~400 MB RAM, minimal battery hit. Browser ~3–5 GB RAM across tabs, significant battery drain.
Multi-file tasks the GUI cannot do
| Task | Terminal | Web GUI |
|---|---|---|
| Rename a function across 30 files | 15 s | 45 min |
| Add a header to 50 HTML pages | 20 s | 2 hrs |
| Update import paths across 100 files after a folder rename | 10 s | impossible |
Find & fix every console.log before deploy |
5 s | 30 min |
| Add error handling to every API call in the project | 30 s | 1 hr |
| Update copyright year in every page footer | 3 s | 20 min |
Web GUI fundamentally has no filesystem access, you'd paste files one at a time, get changes one at a time, apply them one at a time.
Security and audit
| Risk | Terminal | Web GUI |
|---|---|---|
| Browser extensions reading your code / API keys | Not possible | Major, extensions can read all page content |
| Clipboard exposure | Minimal (direct file writes) | Every copy-paste puts code on the clipboard |
| Session hijacking | API key in env var | Session cookie in browser (XSS surface) |
| Data in browser cache | None | Conversations cached in localStorage |
| Audit trail | Shell history (local file) | Proprietary, deletable |
API keys in the terminal are stored in environment variables, never displayed, OS-file-permission protected, invisible to browser extensions, and not sent to analytics or tracking services.
Where the terminal is the only option
- Remote servers & SSH, you can't open claude.ai on a headless server.
ssh prod && claudeis the whole workflow. Read nginx logs, fix config, restart service, one prompt. - Persistent sessions —
tmux new -s ai-session+claude, detach, close SSH, go home. Come back next morning,tmux attach -t ai-session, Claude is still there reporting what it found. - CI/CD —
claude --print "run the release checklist"in a GitHub Action, Vercel hook, or cron job. The browser cannot be scripted. - MCP connectors, Google Drive, Slack, GitHub, Playwright, databases. All terminal-only. The web GUI has zero MCP support.
When the GUI actually wins
- You want an image or voice conversation.
- You're on a Chromebook / iPad where you can't install tools.
- You want to share a conversation link.
- You're drag-dropping a PDF for one-off analysis.
- You're walking or driving and want voice interaction (mobile app).
That's ~10–20 % of professional AI coding work. The terminal handles the other 80–90 % faster, safer, and more reliably.
How Claude Code CLI + Netlify CLI work together for easy administration
Once both are installed and authenticated, the whole admin surface of a website collapses into prompts. Here's the pattern for the most common ops:
Deploy a change end-to-end
cd ~/my-website
claude
> add a privacy policy page that covers cookies and analytics, link it
from the footer, run the build, and deploy a draft to Netlify
Claude writes the page, updates the footer, runs npm run build (or npx @11ty/eleventy), runs netlify deploy, returns the preview URL. You check the preview in a browser, then:
> looks good, deploy to production and ping IndexNow
Add a redirect without touching config files
> add a 301 redirect from /old-page to /new-page in netlify.toml
and verify it works after deploy with curl
Claude edits netlify.toml, deploys, runs curl -I https://site.com/old-page to check for the Location: header. One prompt, proof-of-fix included.
Set environment variables securely
> set a Netlify environment variable SENDGRID_API_KEY to the value
I'll paste next, then redeploy
Claude uses netlify env:set SENDGRID_API_KEY ... (the value stays in the terminal session, never written to a file). Then it triggers a redeploy so the new env is picked up.
Investigate a production issue
> pull the last 3 Netlify deploy logs, summarize any errors, and
suggest a fix for each
netlify api listSiteDeploys returns the deploy history; Claude reads the logs, spots the pattern (build failure, timeout, bad env), and proposes the patch.
Migrate or clone a site
> this folder is a working Eleventy site. Create a new Netlify site
called "alpha-staging", link this folder to it, and do a prod deploy
Claude runs netlify sites:create --name alpha-staging, then netlify link --name alpha-staging, then netlify deploy --dir=_site --prod. Zero clicks in the Netlify dashboard.
Rollback fast
> the last deploy broke the homepage, restore the previous production
deploy
Claude uses netlify api listSiteDeploys to find the previous successful deploy ID, then netlify api restoreSiteDeploy to roll back. ~5 seconds.
The through-line: every Netlify admin action has a CLI equivalent, and every CLI equivalent is something Claude Code can run from a prompt. Once you accept that, the Netlify dashboard becomes a read-only status page and everything mutating happens via prompt.
Cross-promote: pair the terminal with the prompt generators on this site
The terminal gives Claude real filesystem + command access. The jwatte.com tool suite gives Claude the exact thing to do. That pairing is the whole productivity story.
Write better prompts before you hand them to Claude:
- Prompt Enhancer, wraps any prompt in research-backed patterns (ExpertPrompting, OPRO, EmotionPrompt, self-evaluation). Paste your rough instruction, pick an intensity, copy the enhanced version, hand it to
claudeorcodex. Companion post →
Generate ready-to-paste prompts from audit data:
- Mega Analyzer, audits one URL across SEO, schema, E-E-A-T, voice, mobile, performance, AI-search. The output ends in a multi-thousand-word AI fix prompt that names exact file paths and expected score deltas. Pipe that into
claudeand you go from "here's what's broken" to "here's the fix, committed and deployed" in one session. - Site Analyzer, same pattern, 70+ checks, different scoring buckets. Also emits a copy-paste AI fix prompt.
- Batch Compare, up to 10 URLs compared side-by-side with a combined AI prompt covering the whole portfolio. Feed it to Claude and fix 10 sites from one session.
- Link Graph, crawls a site, finds orphans / hubs / dead-ends / noindex pages, and emits an AI fix prompt that proposes exact internal-link additions (source page + destination + anchor text). Paste, tell Claude "apply every recommendation, commit grouped by destination page, deploy", done.
Scaffold new sites from a prompt:
- Single Site Gen, emits a full AI site-build prompt with every best practice (schema, llms.txt, IndexNow, security headers, E-E-A-T signals, WCAG 2.2) baked in.
claude < singlesitegen-prompt.txtand Claude scaffolds the whole thing in~/new-site/. - Monoclone Generator, for when you're spinning up an industry site from a template. Generates the deploy prompt for the whole network.
Generate JSON-LD / schema blocks Claude can drop in directly:
- E-E-A-T Generator, Person / Organization / sameAs / Wikidata / ORCID / rel=me JSON-LD from a single author profile.
- Speakable Generator, SpeakableSpecification JSON-LD for voice + AI-citation.
- FAQ Harvester, pulls every FAQ from the Google top 10, dedupes, emits ready-to-paste FAQPage JSON-LD.
- ItemList / Carousel, Google-compliant ItemList JSON-LD from a URL list.
Audit without leaving the terminal:
- .well-known Audit, 13-file audit of
/.well-known/with copy-paste fix kit. - ai.txt Generator, Spawning-style AI training opt-in/out policy across every bot in the shared registry (robots.txt companion, Netlify/Apache deploy config).
Concrete workflow that ties all of it together:
# 1. Browser: run /tools/mega-analyzer/ on your site, click "Copy AI fix prompt"
# 2. Terminal:
cd ~/my-site
claude
> [paste the Mega Analyzer fix prompt]
# Claude works through every check, edits the files, commits
> run a mobile parity check, then deploy to Netlify prod if pass
# Claude runs /tools/mobile-parity/ mentally, or you paste its URL as input
The tools produce prompts; the terminal consumes prompts. The browser is just the scratchpad where you collect the prompt.
When to reach for which tool
- Editing a real project, multi-file changes, deploys. → Claude Code. It's the only one that reads + writes files end-to-end in your working directory.
- Quick one-off code snippet or explanation. → Codex or
chatgptone-shot mode. Faster, cheaper, no project context needed. - "What's the shell command for X?" → Copilot CLI. Specifically trained for the one-line shell suggestion task.
- GUI preference / on a tablet or Chromebook. → claude.ai/code (browser-based Claude Code), chatgpt.com, VS Code Copilot extension.
The terminal versions are faster for anything that involves your local repo. The GUI versions are better for pure conversation or when you can't install locally.
Related reading
Next up — the follow-on walkthrough: You've Got The Claude Code CLI Installed — Now What? picks up where this post leaves off. Plan mode, CLAUDE.md as a living contract, the slash commands that matter (/btw /fork /rewind /compact /loop /schedule /simplify /batch /powerup /insights /debug /claude-api), Skills vs Rules vs Memory, Auto Mode boundaries, and a Day 0 → Day 7 ramp plan.
Companion coverage from the methodology stack:
- The $97 Launch, Chapter 1 (domain + hosting + deploy). Claude Code + Netlify CLI is the end-state of the workflow that chapter sets up.
- The $20 Dollar Agency, Chapters 5-11 (SEO, schema, keywords). Every audit pattern the Site Analyzer and Mega Analyzer run is something Claude Code can implement in one prompt once the environment is live.
- The $100 Network, Chapter 6 (the provider stack) + Chapter 26 (monitoring at scale). Where the AI terminal fits when you're running more than one site.