Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ DesktopSetup is the Cloud Security Alliance's machine bootstrap. Scripts manage

**macOS (Bash):**
- `macos-work-tools.sh` — Core work apps (1Password, Slack, Zoom, Chrome, Office, Git, GitHub CLI) + optional dev profile (VS Code, AWS CLI, Wrangler). Post-install: `gh auth login` + Git identity from GitHub profile
- `macos-ai-tools.sh` — AI desktop apps (Claude Desktop, ChatGPT) + Git, GitHub CLI + auth + Git identity from GitHub profile, AI coding CLIs (Claude Code, Codex, Gemini) with migration from wrong install methods. Registers accessible CSA plugin marketplaces with Claude Code (via `gh`-probed access check)
- `macos-update.sh` — Updates everything: Homebrew formulas/casks, npm globals, pip packages, Claude Code (`claude update`), plus syncs CSA plugin marketplaces (adds missing accessible ones, refreshes all registered). Snapshots all versions before updating for rollback.
- `macos-plugins.sh` — Standalone plugin install/update. Just the plugin-related work from `macos-update.sh` (register CSA marketplaces, install default plugins, refresh marketplaces) without the Homebrew/npm/pip steps. Use when you just want to get current on plugins without the full update cycle.
- `macos-ai-tools.sh` — AI desktop apps (Claude Desktop, ChatGPT) + Git, GitHub CLI + auth + Git identity from GitHub profile, AI coding CLIs (Claude Code, Codex, Gemini) with migration from wrong install methods. Registers accessible CSA plugin marketplaces with Claude Code (via `gh`-probed access check) and the CSA MCP server (`csa-mcp`) for users with CSA-Internal access
- `macos-update.sh` — Updates everything: Homebrew formulas/casks, npm globals, pip packages, Claude Code (`claude update`), plus syncs CSA plugin marketplaces (adds missing accessible ones, refreshes all registered) and registers the CSA MCP server if missing. Snapshots all versions before updating for rollback.
- `macos-plugins.sh` — Standalone plugin install/update. Just the plugin-related work from `macos-update.sh` (register CSA marketplaces, install default plugins, refresh marketplaces, register CSA MCP server) without the Homebrew/npm/pip steps. Use when you just want to get current on plugins without the full update cycle.
- `macos-mcp-setup.sh` — Configures MCP servers (Airtable, GitHub, Gmail) for Claude Code, Codex, and Gemini. Discovers tokens from existing config files and environment, validates them against each service's API, and writes to each tool's config.

**Windows (PowerShell):**
- `windows-work-tools.ps1` — Same tool set as macOS work tools, using winget instead of Homebrew
- `windows-ai-tools.ps1` — Same AI tools as macOS (desktop apps + CLIs), using winget + npm. Includes migration support, Git identity from GitHub profile, and CSA plugin marketplace registration.
- `windows-ai-tools.ps1` — Same AI tools as macOS (desktop apps + CLIs), using winget + npm. Includes migration support, Git identity from GitHub profile, CSA plugin marketplace registration, and CSA MCP server registration.
- `windows-plugins.ps1` — Standalone plugin install/update, Windows counterpart to `macos-plugins.sh`. Runs just the plugin workflow without touching winget apps.

**Cross-platform:**
Expand Down Expand Up @@ -85,7 +85,7 @@ Requires Python 3 (the script calls `abort` if `python3` is not found). Gmail is
All scripts declare `SCRIPT_VERSION="YYYY.MMDDHHSS"` near the top. Update this value when making changes — use the current date/time in that format.

### Shared boilerplate
All scripts (both platforms) duplicate their output helpers, precondition checks, and utility functions. macOS uses `has_command`, `confirm`, `ensure_brew_in_path`; Windows uses `Has-Command`. The two macOS install scripts additionally share `install_xcode_cli_tools`, `install_homebrew`, `install_node`, `setup_gh_auth`, and `setup_git_identity`. The `CSA_MARKETPLACES` array (list of plugin marketplace `ORG/REPO` strings) is duplicated across **five** scripts: `macos-ai-tools.sh`, `windows-ai-tools.ps1`, `macos-update.sh`, `macos-plugins.sh`, `windows-plugins.ps1` — update all five when adding a new marketplace, and bump each file's `SCRIPT_VERSION`. **When changing shared logic, update all files that use it.** The marketplace-name → repo mapping is similarly duplicated across all five scripts: as a `plugin_marketplace_repo` bash function in the three `.sh` files (function-based because macOS ships bash 3.2, which doesn't support `declare -A` associative arrays), and as a `$PluginMarketplaceRepos` hashtable in the two `.ps1` files. The actual plugin lists, however, are single-source: `scripts/csa-plugins.txt` and `scripts/csa-plugins-internal.txt` are fetched from HEAD at runtime, so list-only changes do **not** require a script edit or `SCRIPT_VERSION` bump.
All scripts (both platforms) duplicate their output helpers, precondition checks, and utility functions. macOS uses `has_command`, `confirm`, `ensure_brew_in_path`; Windows uses `Has-Command`. The two macOS install scripts additionally share `install_xcode_cli_tools`, `install_homebrew`, `install_node`, `setup_gh_auth`, and `setup_git_identity`. The `CSA_MARKETPLACES` array (list of plugin marketplace `ORG/REPO` strings) is duplicated across **five** scripts: `macos-ai-tools.sh`, `windows-ai-tools.ps1`, `macos-update.sh`, `macos-plugins.sh`, `windows-plugins.ps1` — update all five when adding a new marketplace, and bump each file's `SCRIPT_VERSION`. **When changing shared logic, update all files that use it.** The marketplace-name → repo mapping is similarly duplicated across all five scripts: as a `plugin_marketplace_repo` bash function in the three `.sh` files (function-based because macOS ships bash 3.2, which doesn't support `declare -A` associative arrays), and as a `$PluginMarketplaceRepos` hashtable in the two `.ps1` files. The same five files also share the CSA MCP server registration logic (`setup_csa_mcp_server` / `Register-CSAMcpServer`) and the constants `CSA_MCP_NAME`, `CSA_MCP_URL`, `CSA_MCP_GATE_REPO` — keep these in sync too. The actual plugin lists, however, are single-source: `scripts/csa-plugins.txt` and `scripts/csa-plugins-internal.txt` are fetched from HEAD at runtime, so list-only changes do **not** require a script edit or `SCRIPT_VERSION` bump.

### Plugin marketplace registration
`macos-ai-tools.sh`, `windows-ai-tools.ps1`, `macos-update.sh`, `macos-plugins.sh`, and `windows-plugins.ps1` share the same silent-by-default registration contract:
Expand All @@ -105,6 +105,15 @@ The plugin-install contract is shared across all five scripts — `macos-ai-tool
7. Preflight preview: each script's plan-display phase also calls `install_plugins_preview` / `Show-PluginsPreview`, which fetches the list files and compares against `claude plugin list` to print a one-liner like `Plugins install up to 2 new (38 already present)`. No `gh`-probes, so the count is an upper bound — CSA plugins the user can't access get filtered at actual install time.
8. List-only changes (adding or removing a plugin from either `.txt` file) require a single commit to `main` and propagate to existing users on their next installer or `macos-update.sh` run — no script edit or `SCRIPT_VERSION` bump.

### CSA MCP server registration
`macos-ai-tools.sh`, `windows-ai-tools.ps1`, `macos-update.sh`, `macos-plugins.sh`, and `windows-plugins.ps1` register the CSA MCP server (`csa-mcp` → `https://cloudsecurityalliance.org/mcp`, HTTP transport, OAuth 2.1 + PKCE) with Claude Code. The server is in soft launch — open to `@cloudsecurityalliance.org` accounts and beta testers — so registration is silent-by-default and gated behind a `gh`-probe of `CloudSecurityAlliance-Internal/CSA-Plugins` (a CSA-membership proxy that mirrors the plugin-marketplace contract):
1. If `claude` or `gh` is missing, or `gh` is unauthenticated, return silently — no chatter for users without a CSA-Internal-eligible GitHub account.
2. If `csa-mcp` is already registered (parsed from `claude mcp list`, matching `^csa-mcp[: ]`), return silently. Re-running `claude mcp add` for an existing entry would either error or — if it succeeded — invalidate the user's authenticated OAuth session, so we never clobber.
3. Otherwise, `gh api repos/CloudSecurityAlliance-Internal/CSA-Plugins` is called as the gate. Non-zero exit → silent skip. Zero exit → run `claude mcp add --transport http --scope user csa-mcp https://cloudsecurityalliance.org/mcp`.
4. **Output is silent unless registration actually happened.** On success, print a `Registered Claude Code MCP server: csa-mcp` line followed by `Run /mcp inside Claude Code to authenticate with the CSA MCP server.` (the OAuth flow is browser-driven and must be initiated by the user). On `add` failure, print a warn line with the captured stderr indented underneath, matching the marketplace-add error format.
5. Currently Claude Code only. Codex and Gemini support OAuth-HTTP MCP transports too but their config formats differ; adding them is future work.
6. **The CSA MCP server is *not* a token-discovery target for `macos-mcp-setup.sh`.** That script's pipeline is built around static bearer tokens (Airtable, GitHub, Gmail) — it discovers, validates, and writes them to each CLI's config. OAuth flows have no token to pre-discover, so `csa-mcp` belongs in the install/update scripts, not in `macos-mcp-setup.sh`.

### Script execution flow
All macOS scripts follow the same pattern: `main` → preconditions → preflight (show plan) → confirm → action steps → summary. `macos-ai-tools.sh` adds a migration layer: `detect_migrations()` runs during preflight, then `migrate_*()` runs before each tool's install to remove wrong-method installs. `macos-update.sh` takes a pre-update snapshot (to `~/Library/Logs/CSA-DesktopSetup/`) before showing the plan, enabling version rollback if updates break something.

Expand Down
49 changes: 48 additions & 1 deletion scripts/macos-ai-tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

set -euo pipefail

SCRIPT_VERSION="2026.04250000"
SCRIPT_VERSION="2026.04271200"

# ── CSA plugin marketplaces ─────────────────────────────────────────
# Plugin marketplaces to register with Claude Code. Each entry is an
Expand Down Expand Up @@ -79,6 +79,22 @@ plugin_marketplace_repo() {
esac
}

# ── CSA MCP server ──────────────────────────────────────────────────
# Registers the CSA MCP server with Claude Code (HTTP transport,
# OAuth 2.1 + PKCE). The server is in soft launch — open to
# @cloudsecurityalliance.org accounts and beta testers — so we gate
# registration behind a `gh`-probe of a canonical CSA-Internal repo,
# matching the silent-by-default contract used for plugin marketplaces.
#
# KEEP IN SYNC: same constants and logic in
# scripts/windows-ai-tools.ps1
# scripts/macos-update.sh
# scripts/macos-plugins.sh
# scripts/windows-plugins.ps1
CSA_MCP_NAME="csa-mcp"
CSA_MCP_URL="https://cloudsecurityalliance.org/mcp"
CSA_MCP_GATE_REPO="CloudSecurityAlliance-Internal/CSA-Plugins"

# ── Output helpers ──────────────────────────────────────────────────

if [[ -t 1 ]]; then
Expand Down Expand Up @@ -360,6 +376,7 @@ preflight() {
# Plugin marketplaces
echo " Plugin marketplaces probe ${#CSA_MARKETPLACES[@]} CSA repos, add any your GitHub account can access"
install_plugins_preview
echo " CSA MCP server register $CSA_MCP_NAME if your GitHub account has CSA-Internal access"

echo ""
}
Expand Down Expand Up @@ -799,6 +816,35 @@ setup_plugin_marketplaces() {
fi
}

# Register the CSA MCP server (csa-mcp) with Claude Code if missing.
# Silent-by-default: returns silently when claude/gh is unavailable, gh
# is unauthenticated, csa-mcp is already registered, or the user lacks
# CSA-Internal access. The server uses OAuth 2.1 + PKCE, so the user
# must run /mcp inside Claude Code to complete the browser sign-in —
# we print that reminder only on a fresh registration.
setup_csa_mcp_server() {
has_command claude || return 0
has_command gh || return 0
gh auth status >/dev/null 2>&1 || return 0

# Already registered? Don't clobber — would invalidate the OAuth session.
if claude mcp list 2>/dev/null | grep -qE "^${CSA_MCP_NAME}[: ]"; then
return 0
fi

# CSA-membership gate via gh probe of a canonical CSA-Internal repo.
gh api "repos/$CSA_MCP_GATE_REPO" >/dev/null 2>&1 || return 0

local add_err
if add_err="$(claude mcp add --transport http --scope user "$CSA_MCP_NAME" "$CSA_MCP_URL" 2>&1 >/dev/null)"; then
success "Registered Claude Code MCP server: $CSA_MCP_NAME"
info "Run /mcp inside Claude Code to authenticate with the CSA MCP server."
else
warn "Failed to register Claude Code MCP server '$CSA_MCP_NAME':"
printf ' %s\n' "${add_err:-<no stderr output>}"
fi
}

# ── Plugin install ──────────────────────────────────────────────────
# Fetch the public and internal plugin list files from HEAD, register
# any missing marketplaces (CSA marketplaces are gh-probed first),
Expand Down Expand Up @@ -1111,6 +1157,7 @@ main() {
setup_claude_env
setup_plugin_marketplaces
install_plugins
setup_csa_mcp_server
summary
}

Expand Down
36 changes: 35 additions & 1 deletion scripts/macos-plugins.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

set -euo pipefail

SCRIPT_VERSION="2026.04250100"
SCRIPT_VERSION="2026.04271200"

# ── CSA plugin marketplaces ─────────────────────────────────────────
# Registered in sync_plugin_marketplaces() regardless of whether
Expand Down Expand Up @@ -67,6 +67,13 @@ plugin_marketplace_repo() {
esac
}

# ── CSA MCP server ──────────────────────────────────────────────────
# See scripts/macos-ai-tools.sh for full rationale. Keep these constants
# and the setup_csa_mcp_server function in sync across all five scripts.
CSA_MCP_NAME="csa-mcp"
CSA_MCP_URL="https://cloudsecurityalliance.org/mcp"
CSA_MCP_GATE_REPO="CloudSecurityAlliance-Internal/CSA-Plugins"

# ── Output helpers ──────────────────────────────────────────────────

if [[ -t 1 ]]; then
Expand Down Expand Up @@ -327,6 +334,31 @@ sync_plugin_marketplaces() {
fi
}

# Register the CSA MCP server (csa-mcp) with Claude Code if missing.
# See scripts/macos-ai-tools.sh setup_csa_mcp_server for full rationale —
# silent unless we actually register, gh-probed CSA-Internal access gate,
# does not clobber existing OAuth sessions.
setup_csa_mcp_server() {
has_command claude || return 0
has_command gh || return 0
gh auth status >/dev/null 2>&1 || return 0

if claude mcp list 2>/dev/null | grep -qE "^${CSA_MCP_NAME}[: ]"; then
return 0
fi

gh api "repos/$CSA_MCP_GATE_REPO" >/dev/null 2>&1 || return 0

local add_err
if add_err="$(claude mcp add --transport http --scope user "$CSA_MCP_NAME" "$CSA_MCP_URL" 2>&1 >/dev/null)"; then
success "Registered Claude Code MCP server: $CSA_MCP_NAME"
info "Run /mcp inside Claude Code to authenticate with the CSA MCP server."
else
warn "Failed to register Claude Code MCP server '$CSA_MCP_NAME':"
printf ' %s\n' "${add_err:-<no stderr output>}"
fi
}

# ── Preflight ───────────────────────────────────────────────────────

preflight() {
Expand All @@ -337,6 +369,7 @@ preflight() {
if has_command claude; then
echo " Plugin marketplaces: refresh registered, add accessible CSA repos"
install_plugins_preview
echo " CSA MCP server : register $CSA_MCP_NAME if your GitHub account has CSA-Internal access"
else
warn "claude CLI not found — install it first via scripts/macos-ai-tools.sh"
abort "Nothing to do without claude CLI."
Expand All @@ -358,6 +391,7 @@ main() {

sync_plugin_marketplaces
install_plugins
setup_csa_mcp_server

info "Refreshing plugin marketplaces"
claude plugin marketplace update || warn "marketplace update failed; continuing"
Expand Down
36 changes: 35 additions & 1 deletion scripts/macos-update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

set -euo pipefail

SCRIPT_VERSION="2026.04242300"
SCRIPT_VERSION="2026.04271200"

# ── CSA plugin marketplaces ─────────────────────────────────────────
# Each update run will add any entries from this list that aren't yet
Expand Down Expand Up @@ -66,6 +66,13 @@ plugin_marketplace_repo() {
esac
}

# ── CSA MCP server ──────────────────────────────────────────────────
# See scripts/macos-ai-tools.sh for full rationale. Keep these constants
# and the setup_csa_mcp_server function in sync across all five scripts.
CSA_MCP_NAME="csa-mcp"
CSA_MCP_URL="https://cloudsecurityalliance.org/mcp"
CSA_MCP_GATE_REPO="CloudSecurityAlliance-Internal/CSA-Plugins"

# ── Output helpers ──────────────────────────────────────────────────

if [[ -t 1 ]]; then
Expand Down Expand Up @@ -281,6 +288,7 @@ preflight() {
if has_command claude; then
echo " Plugin marketplaces: refresh registered, add accessible CSA repos"
install_plugins_preview
echo " CSA MCP server : register $CSA_MCP_NAME if your GitHub account has CSA-Internal access"
echo ""
fi
}
Expand Down Expand Up @@ -394,6 +402,31 @@ sync_plugin_marketplaces() {
claude plugin marketplace update || warn "marketplace update failed; continuing"
}

# Register the CSA MCP server (csa-mcp) with Claude Code if missing.
# See scripts/macos-ai-tools.sh setup_csa_mcp_server for full rationale —
# silent unless we actually register, gh-probed CSA-Internal access gate,
# does not clobber existing OAuth sessions.
setup_csa_mcp_server() {
has_command claude || return 0
has_command gh || return 0
gh auth status >/dev/null 2>&1 || return 0

if claude mcp list 2>/dev/null | grep -qE "^${CSA_MCP_NAME}[: ]"; then
return 0
fi

gh api "repos/$CSA_MCP_GATE_REPO" >/dev/null 2>&1 || return 0

local add_err
if add_err="$(claude mcp add --transport http --scope user "$CSA_MCP_NAME" "$CSA_MCP_URL" 2>&1 >/dev/null)"; then
success "Registered Claude Code MCP server: $CSA_MCP_NAME"
info "Run /mcp inside Claude Code to authenticate with the CSA MCP server."
else
warn "Failed to register Claude Code MCP server '$CSA_MCP_NAME':"
printf ' %s\n' "${add_err:-<no stderr output>}"
fi
}

# ── Plugin install ──────────────────────────────────────────────────
# Fetch the public and internal plugin list files from HEAD, register
# any missing marketplaces (CSA marketplaces are gh-probed first),
Expand Down Expand Up @@ -657,6 +690,7 @@ main() {
update_claude_code
sync_plugin_marketplaces
install_plugins
setup_csa_mcp_server
summary
}

Expand Down
Loading