diff --git a/.github/workflows/repo-health.yml b/.github/workflows/repo-health.yml
index 2d868ac..00c8174 100644
--- a/.github/workflows/repo-health.yml
+++ b/.github/workflows/repo-health.yml
@@ -5,7 +5,7 @@ on:
pull_request:
jobs:
- structure:
+ structure-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
@@ -13,3 +13,13 @@ jobs:
shell: pwsh
run: |
./scripts/check-structure.ps1
+
+ structure-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Check repo structure
+ shell: bash
+ run: |
+ bash scripts/check-structure.sh
+ bash -n macos/bootstrap.sh
diff --git a/README.md b/README.md
index a12cbcd..407b0c2 100644
--- a/README.md
+++ b/README.md
@@ -68,11 +68,19 @@ So we built it.
## The Solution
+### Windows
+
```powershell
irm https://raw.githubusercontent.com/Jarvis-AojDevStuio/dev-bootstrap/main/bootstrap.ps1 | iex
```
-One line. Paste it in PowerShell. Walk away. Come back to a fully configured development machine.
+### macOS
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/Jarvis-AojDevStuio/dev-bootstrap/main/macos/bootstrap.sh | bash
+```
+
+One command. Paste it in your terminal. Walk away. Come back to a configured development machine.
@@ -87,14 +95,14 @@ One line. Paste it in PowerShell. Walk away. Come back to a fully configured dev
| **fnm** | Latest | Node version manager | [github.com/Schniz/fnm](https://github.com/Schniz/fnm) via winget |
| **Node.js** | LTS | JavaScript runtime | Installed via fnm |
| **npm** | Bundled | Package manager | Comes with Node.js |
-| **uv** | Latest | Python toolchain | [astral.sh/uv](https://docs.astral.sh/uv/) |
+| **uv** | Latest | Python toolchain | [astral-sh.uv](https://docs.astral.sh/uv/) via winget |
| **Python** | 3.12 (pinned) | Python runtime | Installed via uv |
-| **Bun** | Latest | Fast JS runtime | [bun.sh](https://bun.sh/docs/installation) |
-| **Claude Code** | Latest | Anthropic AI CLI | [claude.ai](https://code.claude.com/docs/en/setup) |
+| **Bun** | Latest | Fast JS runtime | [Oven-sh.Bun](https://bun.sh) via winget |
+| **Claude Code** | Latest | Anthropic AI CLI | [@anthropic-ai/claude-code](https://www.npmjs.com/package/@anthropic-ai/claude-code) via npm |
| **Codex CLI** | Latest | OpenAI code assistant | [openai.com](https://developers.openai.com/codex/cli) |
| **WSL2** | Latest | Linux on Windows | [Microsoft](https://learn.microsoft.com/en-us/windows/wsl/install) *(optional)* |
-> Every tool is installed from its **official canonical source**. No third-party mirrors. No mystery binaries. All sources documented inline in `bootstrap.ps1`.
+> Every tool is installed from its **official canonical source** via winget or npm — avoiding `irm|iex` patterns that corporate proxies (Zscaler, etc.) often block. No third-party mirrors. No mystery binaries. All sources documented inline in `bootstrap.ps1`.
## How It Works
diff --git a/bootstrap.ps1 b/bootstrap.ps1
index 7f57db4..7456ac6 100644
--- a/bootstrap.ps1
+++ b/bootstrap.ps1
@@ -3,9 +3,9 @@ Windows-first bootstrap.
Canonical sources:
- WSL: https://learn.microsoft.com/en-us/windows/wsl/install
-- uv: https://docs.astral.sh/uv/getting-started/installation/
-- Bun: https://bun.com/docs/installation
-- Claude Code: https://code.claude.com/docs/en/setup
+- uv: https://github.com/microsoft/winget-pkgs (astral-sh.uv)
+- Bun: https://github.com/microsoft/winget-pkgs (Oven-sh.Bun)
+- Claude Code: https://www.npmjs.com/package/@anthropic-ai/claude-code
- Codex CLI: https://developers.openai.com/codex/cli
- Git for Windows: https://git-scm.com/download/win
- winget: https://learn.microsoft.com/en-us/windows/package-manager/winget/
@@ -126,8 +126,8 @@ node --version 2>$null | Out-Host
npm --version 2>$null | Out-Host
Section "Python via uv (includes Python management)"
-# Official uv PowerShell install: https://docs.astral.sh/uv/getting-started/installation/
-powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+# winget avoids Zscaler/corporate-proxy blocks on irm|iex pattern
+WinGetInstall "astral-sh.uv"
RefreshPath
Need "uv" "If this is your first run, open a new PowerShell window and rerun so PATH updates apply."
uv --version 2>$null | Out-Host
@@ -137,15 +137,15 @@ Need "python" "If this is your first run, open a new PowerShell window and rerun
python --version 2>$null | Out-Host
Section "Bun"
-# Official Bun PowerShell install: https://bun.com/docs/installation
-powershell -c "irm bun.sh/install.ps1 | iex"
+# winget avoids Zscaler/corporate-proxy blocks on irm|iex pattern
+WinGetInstall "Oven-sh.Bun"
RefreshPath
Need "bun" "If this is your first run, open a new PowerShell window and rerun so PATH updates apply."
bun --version 2>$null | Out-Host
Section "Claude Code"
-# Official setup docs: https://code.claude.com/docs/en/setup
-irm https://claude.ai/install.ps1 | iex
+# npm avoids Zscaler/corporate-proxy blocks on irm|iex pattern
+npm install -g @anthropic-ai/claude-code
RefreshPath
Need "claude" "If this is your first run, open a new PowerShell window and rerun so PATH updates apply."
claude --version 2>$null | Out-Host
diff --git a/macos/bootstrap.sh b/macos/bootstrap.sh
new file mode 100755
index 0000000..941b5a7
--- /dev/null
+++ b/macos/bootstrap.sh
@@ -0,0 +1,149 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# macOS bootstrap entrypoint
+# Installs: Xcode CLT, Homebrew, git, curl, fnm (Node), uv (Python), bun, Claude Code, Codex CLI
+
+MARKER_BEGIN="# ---- dev-bootstrap (macos) ----"
+MARKER_END="# ------------------------------"
+
+log() {
+ printf "\n== %s ==\n" "$1"
+}
+
+require_cmd() {
+ command -v "$1" >/dev/null 2>&1 || {
+ echo "Missing command: $1" >&2
+ return 1
+ }
+}
+
+ensure_line_in_file_once() {
+ # Usage: ensure_line_in_file_once
+ local file="$1"
+ local line="$2"
+ mkdir -p "$(dirname "$file")"
+ touch "$file"
+ if ! grep -Fqs "$line" "$file"; then
+ printf "\n%s\n" "$line" >> "$file"
+ fi
+}
+
+ensure_snippet_in_zshrc_once() {
+ local zshrc="$HOME/.zshrc"
+ local snippet_file="$1"
+
+ touch "$zshrc"
+
+ if grep -Fqs "$MARKER_BEGIN" "$zshrc"; then
+ return 0
+ fi
+
+ {
+ echo
+ echo "$MARKER_BEGIN"
+ cat "$snippet_file"
+ echo "$MARKER_END"
+ } >> "$zshrc"
+}
+
+log "Preflight: Xcode Command Line Tools"
+if ! xcode-select -p >/dev/null 2>&1; then
+ echo "Xcode Command Line Tools not found. Triggering install..."
+ xcode-select --install || true
+ echo "Waiting for Command Line Tools installation to complete..."
+ # Poll until installed (user must complete GUI prompt)
+ until xcode-select -p >/dev/null 2>&1; do
+ sleep 5
+ done
+fi
+
+log "Homebrew"
+if ! command -v brew >/dev/null 2>&1; then
+ echo "Homebrew not found. Installing..."
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+fi
+
+# Initialize brew in future shells (idempotent)
+if [[ -x /opt/homebrew/bin/brew ]]; then
+ ensure_line_in_file_once "$HOME/.zprofile" 'eval "$(/opt/homebrew/bin/brew shellenv)"'
+ eval "$(/opt/homebrew/bin/brew shellenv)"
+elif [[ -x /usr/local/bin/brew ]]; then
+ ensure_line_in_file_once "$HOME/.zprofile" 'eval "$(/usr/local/bin/brew shellenv)"'
+ eval "$(/usr/local/bin/brew shellenv)"
+else
+ # If brew is installed somewhere else, just require it and continue.
+ require_cmd brew
+fi
+
+log "Base tools (git, curl)"
+# Idempotent: brew will verify or install.
+brew list git >/dev/null 2>&1 || brew install git
+brew list curl >/dev/null 2>&1 || brew install curl
+
+log "Node via fnm"
+brew list fnm >/dev/null 2>&1 || brew install fnm
+
+# Ensure fnm activation + PATH in zsh
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+SNIPPET="$REPO_ROOT/macos/zshrc.snippet.sh"
+require_cmd fnm
+ensure_snippet_in_zshrc_once "$SNIPPET"
+
+# Activate fnm in current shell
+# shellcheck disable=SC2046
+if fnm env --use-on-cd >/dev/null 2>&1; then
+ eval "$(fnm env --use-on-cd)"
+fi
+
+fnm install --lts
+fnm default lts-latest
+require_cmd node
+require_cmd npm
+node -v
+npm -v
+
+log "Python via uv"
+if ! command -v uv >/dev/null 2>&1; then
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+fi
+
+# Ensure PATH contains uv default install location
+# (actual PATH injection is handled by zsh snippet as well)
+require_cmd uv
+uv --version
+uv python install 3.12
+uv python pin 3.12
+require_cmd python
+python --version
+
+log "Bun"
+if ! command -v bun >/dev/null 2>&1; then
+ curl -fsSL https://bun.com/install | bash
+fi
+require_cmd bun
+bun --version
+
+log "Claude Code"
+if ! command -v claude >/dev/null 2>&1; then
+ # Per official docs for macOS/Linux
+ curl -fsSL https://claude.ai/install.sh | bash
+fi
+if command -v claude >/dev/null 2>&1; then
+ claude --version || true
+else
+ echo "Claude Code installed, but 'claude' is not on PATH yet. Open a new terminal and try: claude --version" >&2
+fi
+
+log "Codex CLI"
+# Requires npm from the fnm-managed node.
+npm list -g --depth=0 @openai/codex >/dev/null 2>&1 || npm install -g @openai/codex
+require_cmd codex
+codex --version
+
+log "Finish"
+echo "Open a NEW terminal so .zprofile/.zshrc changes load."
+echo "Then run (interactive auth):"
+echo " - claude"
+echo " - codex"
diff --git a/macos/zshrc.snippet.sh b/macos/zshrc.snippet.sh
new file mode 100644
index 0000000..e982167
--- /dev/null
+++ b/macos/zshrc.snippet.sh
@@ -0,0 +1,9 @@
+# Minimal PATH + fnm activation for dev-bootstrap (macOS)
+
+# Ensure common installer bins are on PATH
+export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"
+
+# fnm (Node manager)
+if command -v fnm >/dev/null 2>&1; then
+ eval "$(fnm env --use-on-cd)"
+fi
diff --git a/prompts/chatgpt-setup.md b/prompts/chatgpt-setup.md
new file mode 100644
index 0000000..a04f946
--- /dev/null
+++ b/prompts/chatgpt-setup.md
@@ -0,0 +1,125 @@
+# ChatGPT Custom Instructions Builder
+
+> **How to use:** Paste the prompt below into a new ChatGPT conversation.
+> The AI will interview you one question at a time, then generate two blocks:
+> 1. **Custom Instructions** — paste into ChatGPT Settings > Custom Instructions
+> 2. **About Me** — a longer profile you can reuse anywhere
+
+---
+
+## Prompt
+
+```text
+You are an AI "Instruction Builder." Well-structured custom instructions help ChatGPT give consistently better, more personalized responses tailored to your work. Your job is to interview me and then generate TWO final blocks:
+
+1) CUSTOM INSTRUCTIONS (short, optimized for ChatGPT's ~1,500 character limit)
+2) ABOUT ME (longer profile/context, no character limit)
+
+## Process Rules
+
+- Ask questions ONE at a time, in the numbered order below.
+- Ask only what you need to fill the template; stop as soon as you have enough.
+- If I answer vaguely, ask a single follow-up only when genuinely required.
+- If I say "skip" or "N/A", move to the next question without follow-up.
+- After question 11, say "Interview complete! Here are your custom blocks:" then output ONLY the two final blocks, each in its own code block.
+- Use placeholders like [NOT PROVIDED] ONLY if I refuse to answer a required field.
+
+## Interview Questions (ask one at a time, in this exact order)
+
+1) What assistant name/persona should I use? (e.g., Jarvis, Nova, Sage)
+2) What is your name?
+3) Your role/title?
+4) Your location (city/state/country)?
+5) List 3 core values (comma-separated).
+6) One-sentence mission statement? (skip if unsure)
+7) Your industry + what you do (1-2 sentences).
+8) Your primary tools/stack (list the main ones).
+9) Your technical level: beginner, intermediate, or advanced?
+10) Any email/document style rules? (e.g., "always use bullet points", "formal tone")
+11) Do you want contact info included in ABOUT ME? If yes, provide any of: website, email, phone, linkedin, company, address.
+
+## Output Requirements
+
+CUSTOM INSTRUCTIONS must fit within ~1,500 characters and include ALL of the following:
+
+- Assistant name/persona line: "Your name is [ASSISTANT_NAME]. Refer to yourself as [ASSISTANT_NAME]."
+- Decision rule (this is critical — put it near the top):
+ - Config/troubleshooting: THE OPTION — single best fix, minimal steps, no alternatives unless asked
+ - Exploratory/strategy: 2-4 options with tradeoffs + your recommendation
+- Clarifying questions only when truly ambiguous
+- Response format tags (always use these labels):
+ - SUMMARY: (one-line answer)
+ - ACTIONS: (steps to take)
+ - RESULTS: (code or final output, always in a code block)
+ - For complex/exploratory topics, also add:
+ - ANALYSIS: (deeper breakdown)
+ - NEXT: (recommended next steps)
+- Code/text rule: any extracted or provided text goes in a code block; code always goes under RESULTS.
+- Web/citations rule: do not browse for simple troubleshooting; browse + cite URLs for time-sensitive or uncertain facts.
+- Risk rule: flag moderate risk with WARNING and high risk/irreversible actions with DANGER.
+
+ABOUT ME must include:
+- Name, role/title, location
+- Core values (3)
+- Mission statement
+- Work context (industry, responsibilities, tools/stack, technical level)
+- Communication preferences (email/doc rules, plus the same decision rule above)
+- Optional contact fields (only if provided)
+
+## Example Output
+
+Here is an example of what the TWO final blocks should look like. Adapt the content based on the user's actual interview answers.
+
+CUSTOM INSTRUCTIONS example:
+
+Your name is Jarvis. Refer to yourself as Jarvis.
+
+Decision rule:
+- Config/troubleshooting: Give THE OPTION. Single best fix, minimal steps. No alternatives unless I ask.
+- Exploratory/strategy: Give 2-4 options with tradeoffs, then your recommendation.
+
+Only ask clarifying questions when genuinely ambiguous.
+
+Format every response with:
+SUMMARY: one-line answer
+ACTIONS: numbered steps to take
+RESULTS: code or final output (always in a code block)
+
+For complex or exploratory topics, add:
+ANALYSIS: deeper breakdown of the problem
+NEXT: recommended next steps
+
+Rules:
+- Any extracted text or code goes in a code block. Code always under RESULTS.
+- Do not browse the web for simple troubleshooting. Browse + cite URLs for time-sensitive or uncertain facts.
+- Flag moderate risk with WARNING. Flag high risk or irreversible actions with DANGER.
+- Keep responses concise and actionable. I prefer direct answers over lengthy explanations.
+
+ABOUT ME example:
+
+Name: Alex Chen
+Role: Senior DevOps Engineer
+Location: Austin, TX
+
+Core Values: Automation, Reliability, Continuous Learning
+
+Mission: Eliminate toil through infrastructure-as-code so teams can focus on building products.
+
+Work Context:
+- Industry: SaaS / Cloud Infrastructure
+- Responsibilities: CI/CD pipelines, Kubernetes clusters, monitoring, incident response
+- Tools/Stack: Terraform, AWS, Kubernetes, GitHub Actions, Python, Go
+- Technical Level: Advanced
+
+Communication Preferences:
+- Emails: concise, bullet points, action items at top
+- Documents: headers + short paragraphs, no filler
+- Decision rule: single best option for troubleshooting, options with tradeoffs for strategy
+
+Contact:
+- GitHub: github.com/alexchen
+- LinkedIn: linkedin.com/in/alexchen
+- Company: Acme Cloud Inc.
+
+Start now with Question 1.
+```
diff --git a/scripts/check-structure.ps1 b/scripts/check-structure.ps1
index b5ec625..3e5dd9a 100644
--- a/scripts/check-structure.ps1
+++ b/scripts/check-structure.ps1
@@ -8,7 +8,10 @@ $requiredPaths = @(
'LICENSE',
'bootstrap.ps1',
'wsl/setup.sh',
+ 'macos/bootstrap.sh',
+ 'macos/zshrc.snippet.sh',
'scripts/check-structure.ps1',
+ 'scripts/check-structure.sh',
'.github/workflows/repo-health.yml'
)
diff --git a/scripts/check-structure.sh b/scripts/check-structure.sh
new file mode 100755
index 0000000..809a215
--- /dev/null
+++ b/scripts/check-structure.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+required_paths=(
+ "README.md"
+ "LICENSE"
+ "bootstrap.ps1"
+ "wsl/setup.sh"
+ "macos/bootstrap.sh"
+ "macos/zshrc.snippet.sh"
+ "scripts/check-structure.ps1"
+ "scripts/check-structure.sh"
+ ".github/workflows/repo-health.yml"
+)
+
+missing=()
+for p in "${required_paths[@]}"; do
+ if [[ ! -e "$p" ]]; then
+ missing+=("$p")
+ fi
+done
+
+if (( ${#missing[@]} > 0 )); then
+ echo "Missing required paths:" >&2
+ for m in "${missing[@]}"; do
+ echo " - $m" >&2
+ done
+ exit 1
+fi
+
+echo "OK: repo structure looks correct."