From 0667fb2cd311494ba29eb5a52c9e9f96b7cb88cc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 16:28:16 +0000 Subject: [PATCH 01/13] Add plan: Mac app setup via Brewfile + toggle TUI + auth helpers https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- plans/2026-06-16-mac-app-setup-brewfile.md | 181 +++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 plans/2026-06-16-mac-app-setup-brewfile.md diff --git a/plans/2026-06-16-mac-app-setup-brewfile.md b/plans/2026-06-16-mac-app-setup-brewfile.md new file mode 100644 index 0000000..d4c8b4d --- /dev/null +++ b/plans/2026-06-16-mac-app-setup-brewfile.md @@ -0,0 +1,181 @@ +# Plan: New-Mac App Setup via Brewfile + Toggle TUI + Auth Helpers + +Date: 2026-06-16 +Branch: `claude/quirky-hypatia-wj5t9g` + +## Goal + +Make setting up a fresh Mac a one-command, reviewable experience: +- Install the GUI apps you actually use (casks + Mac App Store). +- Browse each app's description and toggle it on/off before installing. +- Reproducible + re-runnable (Brewfile). +- Encode the ChatGPT trust policy (Homebrew only, official casks, **no new taps**, MAS for App Store vendor apps). +- Prune dotfiles cruft that the security policy says to drop. +- Help with the apps that need auth/manual login. + +Priority order baked into every decision (from your security note): +**Security > Reliability > Reproducibility > Performance > Novelty.** + +--- + +## 1. Architecture + +``` +config/apps.conf # NEW — single source of truth: one line per app + └─ generates → config/Brewfile # brew/cask/mas entries (committed, reproducible) +custom_bins/app-picker # NEW — gum TUI: browse descriptions, toggle, write Brewfile +install.sh --apps # NEW component: bootstrap brew+gum → run picker → brew bundle +scripts/setup/auth-setup # NEW — interactive post-install auth checklist +``` + +Why Brewfile (your choice) + a registry: +- `brew bundle` natively handles `brew "x"`, `cask "x"`, and `mas "App", id: N` — one mechanism covers CLI tools, GUI casks, and App Store apps. +- The committed `config/Brewfile` is the reproducible lock-ish artifact the policy asks for. +- `config/apps.conf` keeps descriptions + category + trust tier + auth notes that a raw Brewfile can't hold; the picker reads it and emits the Brewfile. + +### `config/apps.conf` schema + +``` +# method | id | category | tier | default | name | description | auth +cask | notion | text | 1 | true | Notion | Notes/docs/wiki | login +mas | 904280696 | tasks | 2 | true | Things 3 | GTD task manager (App Store) | things-cloud +brew | wakatime-cli | time | 2 | false | WakaTime CLI | Coding time tracker (API key) | apikey +``` + +- **method**: `brew` (formula) / `cask` / `mas` (App Store). +- **tier**: 1 = official vendor auto-approve, 2 = mature OSS review, 3 = needs explicit approval (per your policy). Drives a color tag in the TUI; tier-3 items default OFF. +- **default**: initial toggle state. +- **auth**: token for the auth-setup checklist (`login`, `apikey`, `pair-phone`, `safari-ext`, `license`, `none`). + +### TUI: `gum` (answers your "which needs no install?") + +None of gum/fzf/ratatui preship on a clean Mac, but: +- Homebrew is the *only* hard prereq and `install.sh` already installs it first. +- `gum` is a 1-file brew formula already in your package list and already the engine behind `show_component_menu`. +- ratatui would need a full `cargo build` (heavy, slow on fresh machine). + +So: **bootstrap `gum` immediately after Homebrew**, then reuse the existing menu pattern. The picker shows `name — description` rows grouped by category, with a tier tag, full description visible inline (gum) — space toggles, enter confirms, writes `config/Brewfile`. (If you'd prefer a full-description side panel, fzf `--preview` is a drop-in alternative; gum is the lower-friction default.) + +--- + +## 2. App → install-method mapping (verified) + +Legend: ✅ cask · 🛒 Mac App Store (mas) · ⌘ formula · ⚙️ already has dotfiles *config* (install layer is new) + +| Category | App | Method | Cask/ID | Tier | Notes | +|---|---|---|---|---|---| +| text | Bear | 🛒 | `1091189122` | 2 | App Store only; `bearcli` deploy already symlinks CLI | +| text | Notion | ✅ | `notion` | 1 | | +| tasks | Things 3 | 🛒 | `904280696` | 2 | App Store only | +| coding/editor | Cursor | ✅ | `cursor` | 1 | editor config already deployed | +| coding/editor | Antigravity | ✅ | `antigravity` | 1 | Google; config already deployed | +| coding/editor | Zed | ✅ | `zed` | 1 | config already deployed ⚙️ | +| coding/LLM | ChatGPT | ✅ | `chatgpt` | 1 | OpenAI | +| coding/LLM | Claude | ✅ | `claude` | 1 | Anthropic desktop | +| coding/LLM | Codex CLI | ⌘ | (npm, existing `ai-tools`) | 1 | already installed | +| coding/CLI | Ghostty | ✅ | `ghostty` | 1 | config already deployed ⚙️ | +| meetings | Granola | ✅ | `granola` | 2 | | +| cloud | Dropbox | ✅ | `dropbox` | 1 | | +| cloud | Google Drive | ✅ | `google-drive` | 1 | optional (default OFF) | +| search | Alfred | ✅ | `alfred` | 2 | prefs sync from Dropbox (manual: set sync folder + Powerpack license) | +| messaging | Slack | ✅ | `slack` | 1 | | +| messaging | Spark | ✅ | `readdle-spark` | 2 | | +| productivity | Mouseless | ✅ | `mouseless` | 2 | config already deployed ⚙️ | +| productivity | PopClip | ✅ | `popclip` | 2 | | +| time | Super Productivity | ✅ | `super-productivity` | 2 | | +| time | WakaTime CLI | ⌘ | `wakatime-cli` | 2 | optional; API key via secrets | +| voice | VoiceInk | ✅ | `voiceink` | 2 | config already deployed ⚙️; downloads model on first run | +| vpn | NordVPN | ✅ | `nordvpn` | 2 | `vpn` deploy already configures split tunnel ⚙️ | +| auth | Bitwarden | 🛒 | `1352778147` | 1 | MAS build needed for the Safari extension | +| auth | 2FAS | 🛒 | *verify id* | 2 | mainly phone-paired; Safari ext | +| auth | Tailscale | ✅ | `tailscale-app` | 1 | `vpn` deploy already configures ⚙️ | +| safari-ext | uBlock Origin Lite | 🛒 | `6745342698` | 2 | enable manually in Safari | +| safari-ext | Userscripts | 🛒 | `1463298887` *verify* | 2 | enable manually in Safari | +| safari-ext | 2FAS / Bitwarden | 🛒 | (above) | — | enable manually in Safari | +| music | Spotify | ✅ | `spotify` | 1 | | +| misc | AlDente | ✅ | `aldente` | 2 | | +| misc | Finicky | ✅ | `finicky` | 2 | currently installed inline → fold into Brewfile ⚙️ | +| misc | AppCleaner | ✅ | `appcleaner` | 2 | | +| misc | CleanShot X | ✅ | `cleanshot` | 1 | | +| misc | Stats | ✅ | `stats` | 2 | | +| misc | KeyboardCleanTool | ✅ | `keyboardcleantool` | 2 | | +| misc | BeardedSpice | ✅ | `beardedspice` | 2 | | +| antivirus | Malwarebytes | ✅ | `malwarebytes` | 2 | **optional, default OFF**; on-demand scanner. Real-time may conflict with Trellix | +| antivirus | Trellix | ❌ | — | — | **University-managed** — NOT in Brewfile. Checklist note only (installed/updated via uni) | + +IDs marked *verify* get a `mas search` / `brew info` check during implementation before committing (policy: `brew info` before install, verify vendor/homepage). Safari extensions can be *installed* but must be *enabled* in Safari manually — the auth checklist will list them. + +### "settings → dotfiles" (menu bar / accessibility / dock) +These aren't apps — they're system defaults. Already handled by `config/macos_settings.sh`. I'll extend it with: +- Dock: which apps are pinned (set from the installed-apps list) + autohide behaviour. +- Menu bar items (where scriptable; Stats/AlDente handle most). +Treated as a follow-up sub-task, not part of the Brewfile. + +--- + +## 3. Auth / manual-setup helper + +New `scripts/setup/auth-setup` (run after `brew bundle`): an interactive gum checklist that, per app needing setup, prints the action and offers to open the app / URL: + +- **git / gh** — already covered (gist sync + `gh auth login`); checklist just verifies. +- **API-key apps (WakaTime)** — wire into existing secrets system (`setup-envrc` / `with-secrets`); no plaintext. +- **GUI logins** (Dropbox, Slack, Spark, Granola, Bitwarden, NordVPN, Tailscale, ChatGPT, Claude, Things Cloud, Spotify) — open app, check off when logged in. Can't be automated (interactive OAuth/passwords) — checklist only. +- **Alfred** — open prefs, point sync folder at Dropbox, apply Powerpack license. +- **Safari extensions** — open Safari → Settings → Extensions, enable uBlock Origin Lite / Bitwarden / 2FAS / Userscripts. +- **VoiceInk** — first-run model download. + +No secrets are stored in plaintext; anything key-based flows through the existing SOPS/BWS path. + +--- + +## 4. Proposed pruning (per-item approval — your choice) + +Driven by your policy ("prefer mature, boring"; "never auto-add taps"; "revisit ZeroBrew in 6–12 months"; "cautious with MCP/random tools"): + +**APPROVED for removal:** + +| # | Remove | Where | Rationale | +|---|---|---|---| +| P1 | **zerobrew** | `install.sh` experimental | Experimental pkg mgr; your note says revisit in 6–12mo. `curl\|bash` install. | +| P3 | **Coven** + `brew tap Crazytieguy/tap` | `install.sh` ai-tools | Third-party tap — violates "never auto-add taps". | + +**KEEP (you declined):** ty type checker, zotero-mcp-server → so the `experimental` component stays. +`OFFICIAL_PLUGINS` audit deferred (can revisit separately). + +--- + +## 5. Policy documentation + +- Extend `claude/rules/supply-chain-security.md` (or add `config/apps.conf` header) with the tier model + "no new taps without approval" + "`brew info` before adding any app". +- Note in `CLAUDE.md` how to add an app (one line in `apps.conf` → re-run picker) and the Brewfile regen flow (`brew bundle dump`-style). +- Optional, not auto-applied: your note prefers `~/code/dotfiles` over `~/.dotfiles`. `DOT_DIR` is auto-detected so nothing breaks either way — I'll mention it but not move the repo. + +--- + +## 6. Files to change + +- **NEW** `config/apps.conf` — app registry (the table above). +- **NEW** `config/Brewfile` — generated, committed. +- **NEW** `custom_bins/app-picker` — gum toggle TUI → writes Brewfile. +- **NEW** `scripts/setup/auth-setup` — auth checklist. +- **EDIT** `config.sh` — add `apps` to `INSTALL_REGISTRY`; remove pruned items (pending approval). +- **EDIT** `install.sh` — `--apps` block: bootstrap gum, run picker, `brew bundle --file=config/Brewfile`; remove inline Finicky + pruned experimental/coven blocks. +- **EDIT** `scripts/shared/helpers.sh` — small `brew bundle` + `mas` helpers if needed. +- **EDIT** `claude/rules/supply-chain-security.md`, `CLAUDE.md` — policy + how-to. +- **EDIT** `config/macos_settings.sh` — dock/menu-bar follow-up (optional, can defer). + +## 7. Verification + +- `mas search` / `brew info` each *verify*-flagged id before committing the Brewfile. +- `brew bundle check --file=config/Brewfile` (dry, on a Mac) — can't run here (Linux container); will gate behind a note for you to run, or validate syntax with a parser. +- `app-picker` run with `--dry-run` to confirm it emits a valid Brewfile without installing. +- Shellcheck the new scripts. + +## 8. Resolved decisions + +1. **Prune**: zerobrew + Coven/tap only (P1, P3). ty, zotero-mcp, `experimental` component all stay. +2. **Antivirus**: Trellix = university-managed (checklist note, not Brewfile). Malwarebytes = optional cask, default OFF, conflict note. +3. **Optional apps**: Google Drive + WakaTime default OFF (toggle on in picker). +4. **TUI**: gum (bootstrapped after Homebrew). fzf `--preview` remains a drop-in alt. + +All open questions resolved — ready to implement on approval. From d88fc45c96ffa4e8357b24fcc0f6cecf027ee4c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 16:31:46 +0000 Subject: [PATCH 02/13] Refine app-setup plan: MAS-first rule, AV guidance, Bitwarden cask correction - Prefer Mac App Store > vendor download > cask (sandbox/least-privilege), cask only when unsandboxed system access needed or not on MAS - Antivirus: recommend Malwarebytes for personal use, skip heavy Trellix EDR, never run two real-time engines - Correct Bitwarden: cask exists for desktop; MAS only needed for Safari ext - Prune list finalized: zerobrew + Coven/tap only https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- plans/2026-06-16-mac-app-setup-brewfile.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plans/2026-06-16-mac-app-setup-brewfile.md b/plans/2026-06-16-mac-app-setup-brewfile.md index d4c8b4d..3db0d90 100644 --- a/plans/2026-06-16-mac-app-setup-brewfile.md +++ b/plans/2026-06-16-mac-app-setup-brewfile.md @@ -42,7 +42,7 @@ mas | 904280696 | tasks | 2 | true | Things 3 | GTD brew | wakatime-cli | time | 2 | false | WakaTime CLI | Coding time tracker (API key) | apikey ``` -- **method**: `brew` (formula) / `cask` / `mas` (App Store). +- **method**: `brew` (formula) / `cask` / `mas` (App Store). **Selection rule (per your policy: MAS > vendor download > cask):** prefer `mas` when the app is on the App Store AND its sandboxed MAS build isn't feature-crippled (gives sandbox/least-privilege + notarization + no cask supply-chain surface). Use `cask` only when the app needs unsandboxed system access (accessibility, automation, system/network extensions, SMC) or isn't on MAS. Safari extensions are always `mas`. Caveat: `mas` re-installs Apple-ID-owned apps, but first acquisition of paid apps (e.g. Things 3) is a one-time GUI step. - **tier**: 1 = official vendor auto-approve, 2 = mature OSS review, 3 = needs explicit approval (per your policy). Drives a color tag in the TUI; tier-3 items default OFF. - **default**: initial toggle state. - **auth**: token for the auth-setup checklist (`login`, `apikey`, `pair-phone`, `safari-ext`, `license`, `none`). @@ -86,7 +86,7 @@ Legend: ✅ cask · 🛒 Mac App Store (mas) · ⌘ formula · ⚙️ already ha | time | WakaTime CLI | ⌘ | `wakatime-cli` | 2 | optional; API key via secrets | | voice | VoiceInk | ✅ | `voiceink` | 2 | config already deployed ⚙️; downloads model on first run | | vpn | NordVPN | ✅ | `nordvpn` | 2 | `vpn` deploy already configures split tunnel ⚙️ | -| auth | Bitwarden | 🛒 | `1352778147` | 1 | MAS build needed for the Safari extension | +| auth | Bitwarden | ✅ or 🛒 | `bitwarden` cask / mas `1352778147` | 1 | Desktop app **has a cask**. Safari extension ships **only** in the MAS build → use mas if you want the Safari ext (covers both) | | auth | 2FAS | 🛒 | *verify id* | 2 | mainly phone-paired; Safari ext | | auth | Tailscale | ✅ | `tailscale-app` | 1 | `vpn` deploy already configures ⚙️ | | safari-ext | uBlock Origin Lite | 🛒 | `6745342698` | 2 | enable manually in Safari | @@ -100,8 +100,8 @@ Legend: ✅ cask · 🛒 Mac App Store (mas) · ⌘ formula · ⚙️ already ha | misc | Stats | ✅ | `stats` | 2 | | | misc | KeyboardCleanTool | ✅ | `keyboardcleantool` | 2 | | | misc | BeardedSpice | ✅ | `beardedspice` | 2 | | -| antivirus | Malwarebytes | ✅ | `malwarebytes` | 2 | **optional, default OFF**; on-demand scanner. Real-time may conflict with Trellix | -| antivirus | Trellix | ❌ | — | — | **University-managed** — NOT in Brewfile. Checklist note only (installed/updated via uni) | +| antivirus | Malwarebytes | ✅ | `malwarebytes` | 2 | **optional, default OFF**; lightweight on-demand scanner. **Recommended** AV for personal use | +| antivirus | Trellix | ❌ | — | 3 | Personal install, no cask → checklist manual-install note. **Don't run real-time alongside Malwarebytes.** Recommend skip (heavy enterprise EDR, low value for dev threat model) | IDs marked *verify* get a `mas search` / `brew info` check during implementation before committing (policy: `brew info` before install, verify vendor/homepage). Safari extensions can be *installed* but must be *enabled* in Safari manually — the auth checklist will list them. From ba80ce61a44d92a64d382d540c7d9514ac2710dd Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 16:33:49 +0000 Subject: [PATCH 03/13] Correct PopClip (MAS abandoned, use cask) and Spark (MAS not crippled) https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- plans/2026-06-16-mac-app-setup-brewfile.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plans/2026-06-16-mac-app-setup-brewfile.md b/plans/2026-06-16-mac-app-setup-brewfile.md index 3db0d90..c529f70 100644 --- a/plans/2026-06-16-mac-app-setup-brewfile.md +++ b/plans/2026-06-16-mac-app-setup-brewfile.md @@ -79,9 +79,9 @@ Legend: ✅ cask · 🛒 Mac App Store (mas) · ⌘ formula · ⚙️ already ha | cloud | Google Drive | ✅ | `google-drive` | 1 | optional (default OFF) | | search | Alfred | ✅ | `alfred` | 2 | prefs sync from Dropbox (manual: set sync folder + Powerpack license) | | messaging | Slack | ✅ | `slack` | 1 | | -| messaging | Spark | ✅ | `readdle-spark` | 2 | | -| productivity | Mouseless | ✅ | `mouseless` | 2 | config already deployed ⚙️ | -| productivity | PopClip | ✅ | `popclip` | 2 | | +| messaging | Spark | 🛒 or ✅ | mas (verify id) / `readdle-spark` cask | 2 | MAS build is full-featured (not crippled) → MAS preferred for sandbox; cask also fine | +| productivity | Mouseless | ✅ | `mouseless` | 2 | config already deployed ⚙️; needs accessibility → cask | +| productivity | PopClip | ✅ | `popclip` | 2 | **Use cask, NOT MAS** — MAS edition abandoned at v2023.9; standalone is sandbox-free + current | | time | Super Productivity | ✅ | `super-productivity` | 2 | | | time | WakaTime CLI | ⌘ | `wakatime-cli` | 2 | optional; API key via secrets | | voice | VoiceInk | ✅ | `voiceink` | 2 | config already deployed ⚙️; downloads model on first run | From 64b4218fcc70179f2ad09557af244302935cb117 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 16:45:41 +0000 Subject: [PATCH 04/13] Add malicious-app/executable defense section to app-setup plan - Install integrity vs runtime defense (Gatekeeper, sha256, XProtect already cover) - Enforce quarantine policy (no --no-quarantine) - spctl/codesign signature-verify step in auth-setup - LuLu optional outbound firewall (default OFF) - Harden curl|bash: prefer brew formula, else fetch-verify-run https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- plans/2026-06-16-mac-app-setup-brewfile.md | 35 +++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/plans/2026-06-16-mac-app-setup-brewfile.md b/plans/2026-06-16-mac-app-setup-brewfile.md index c529f70..6dbcdfb 100644 --- a/plans/2026-06-16-mac-app-setup-brewfile.md +++ b/plans/2026-06-16-mac-app-setup-brewfile.md @@ -152,30 +152,51 @@ Driven by your policy ("prefer mature, boring"; "never auto-add taps"; "revisit --- -## 6. Files to change +## 6. Malicious apps & executables — install integrity + runtime defense + +Trust tiers gate *what* we install; this section gates *integrity* (is the bytes what the vendor shipped?) and *runtime* (is a trusted-looking app misbehaving?). All additions are official/mature, free unless noted. + +### Already covered +- **macOS**: Gatekeeper + notarization (blocks unsigned/un-notarized on launch), XProtect + XProtect Remediator (Apple malware scanner, auto-updated), App Store sandboxing (MAS apps = highest trust → "MAS-first" rule), TCC permission prompts. +- **Brew casks**: pinned **sha256** verified on download → tampered artifact aborts. +- **Dev deps** (existing): `min-release-age` 7-day quarantine, `ignore-scripts`, weekly `dep-audit`, Socket CLI, gitleaks, pip-audit. + +### Additions (all selected) +1. **Enforce quarantine policy** — never `--no-quarantine` in any cask/Brewfile entry; document that Gatekeeper/notarization must stay enabled. Pure policy, zero cost. → `claude/rules/supply-chain-security.md`. +2. **Signature-verify step in `auth-setup`** — after install, run `spctl --assess --type execute` + `codesign -dv --verbose=4` per app; report any unsigned/un-notarized app before you trust it. Free. +3. **LuLu** (Objective-See, free OSS outbound firewall) — optional cask, **default OFF** (prompts a lot). Fills the runtime/egress gap: catches a signed-but-compromised app phoning home. Document KnockKnock (persistence enumeration) + BlockBlock (persistence alerts) as further optional Objective-See tools. +4. **Harden `curl|bash` installers** — resolves "is official-page curl|bash ok?": + - Official page gives **authenticity** (HTTPS cert proves the domain) but NOT **integrity-over-time** (runs whatever's live, unseen), **pinning** (no agreed sha → tamper passes), or **reproducibility**. + - **Rule, best→worst:** (a) use the official **brew formula** if it exists — `uv`, `rustup-init`, `bun` all do; you get the vendor's artifact + sha pin + reproducible re-run. (b) No formula → `curl -o` the script, verify vendor checksum/signature if published, inspect, then run — never blind-pipe. (c) blind `curl … | sh` only as last resort, HTTPS-to-official-domain only. + - Migrate existing blind pipes in `install.sh` (uv, rust) to brew formulae / fetch-verify-run. + +--- + +## 7. Files to change - **NEW** `config/apps.conf` — app registry (the table above). - **NEW** `config/Brewfile` — generated, committed. - **NEW** `custom_bins/app-picker` — gum toggle TUI → writes Brewfile. -- **NEW** `scripts/setup/auth-setup` — auth checklist. -- **EDIT** `config.sh` — add `apps` to `INSTALL_REGISTRY`; remove pruned items (pending approval). -- **EDIT** `install.sh` — `--apps` block: bootstrap gum, run picker, `brew bundle --file=config/Brewfile`; remove inline Finicky + pruned experimental/coven blocks. +- **NEW** `scripts/setup/auth-setup` — auth checklist + `spctl`/`codesign` signature-verify step (§6.2). +- **EDIT** `config.sh` — add `apps` to `INSTALL_REGISTRY`; remove pruned items; add LuLu (optional, OFF) to apps.conf. +- **EDIT** `install.sh` — `--apps` block: bootstrap gum, run picker, `brew bundle --file=config/Brewfile`; remove inline Finicky + pruned experimental/coven blocks; migrate uv/rust `curl|bash` → brew formulae / fetch-verify-run (§6.4). - **EDIT** `scripts/shared/helpers.sh` — small `brew bundle` + `mas` helpers if needed. -- **EDIT** `claude/rules/supply-chain-security.md`, `CLAUDE.md` — policy + how-to. +- **EDIT** `claude/rules/supply-chain-security.md`, `CLAUDE.md` — policy + how-to + quarantine/no-`--no-quarantine` rule + curl|bash hardening rule. - **EDIT** `config/macos_settings.sh` — dock/menu-bar follow-up (optional, can defer). -## 7. Verification +## 8. Verification - `mas search` / `brew info` each *verify*-flagged id before committing the Brewfile. - `brew bundle check --file=config/Brewfile` (dry, on a Mac) — can't run here (Linux container); will gate behind a note for you to run, or validate syntax with a parser. - `app-picker` run with `--dry-run` to confirm it emits a valid Brewfile without installing. - Shellcheck the new scripts. -## 8. Resolved decisions +## 9. Resolved decisions 1. **Prune**: zerobrew + Coven/tap only (P1, P3). ty, zotero-mcp, `experimental` component all stay. 2. **Antivirus**: Trellix = university-managed (checklist note, not Brewfile). Malwarebytes = optional cask, default OFF, conflict note. 3. **Optional apps**: Google Drive + WakaTime default OFF (toggle on in picker). 4. **TUI**: gum (bootstrapped after Homebrew). fzf `--preview` remains a drop-in alt. +5. **Malicious apps/executables** (§6): enforce quarantine policy + `spctl`/`codesign` verify step + LuLu (optional, OFF) + harden `curl|bash` → prefer brew formula, else fetch-verify-run. All four selected. All open questions resolved — ready to implement on approval. From 228a0af0e3a5b5c945103853d32357b6886e6982 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 17:12:40 +0000 Subject: [PATCH 05/13] Add app registry, Brewfile picker TUI, and auth-setup checklist - config/apps.conf: single-source registry (method|id|category|tier|default|name|desc|auth) for 34 GUI/App Store apps; official casks + mas only, no third-party taps - custom_bins/app-picker: gum toggle TUI reads apps.conf, generates config/Brewfile (brew/cask/mas sections); --defaults/--dry-run for non-interactive use - config/Brewfile: generated default selection (32 apps) - scripts/setup/auth-setup: post-install login checklist + spctl/codesign signature+notarization audit; App Store sign-in caveat documented --- config/Brewfile | 43 +++++++++++++ config/apps.conf | 106 ++++++++++++++++++++++++++++++ custom_bins/app-picker | 135 +++++++++++++++++++++++++++++++++++++++ scripts/setup/auth-setup | 105 ++++++++++++++++++++++++++++++ 4 files changed, 389 insertions(+) create mode 100644 config/Brewfile create mode 100644 config/apps.conf create mode 100755 custom_bins/app-picker create mode 100755 scripts/setup/auth-setup diff --git a/config/Brewfile b/config/Brewfile new file mode 100644 index 0000000..a7c7af7 --- /dev/null +++ b/config/Brewfile @@ -0,0 +1,43 @@ +# Brewfile — GENERATED by app-picker from config/apps.conf. Do not edit by hand. +# Regenerate: app-picker (or app-picker --defaults) +# Install: brew bundle --file=config/Brewfile +# Policy: official casks + Mac App Store only; never --no-quarantine. + +# mas-cli drives Mac App Store installs (must be signed into the App Store). +brew "mas" + +# ── Casks (official Homebrew casks) ── +cask "aldente" +cask "alfred" +cask "antigravity" +cask "appcleaner" +cask "beardedspice" +cask "chatgpt" +cask "claude" +cask "cleanshot" +cask "cursor" +cask "dropbox" +cask "finicky" +cask "ghostty" +cask "granola" +cask "keyboardcleantool" +cask "mouseless" +cask "nordvpn" +cask "notion" +cask "popclip" +cask "readdle-spark" +cask "slack" +cask "spotify" +cask "stats" +cask "super-productivity" +cask "tailscale-app" +cask "voiceink" +cask "zed" + +# ── Mac App Store (sandboxed, Apple-reviewed) ── +mas "2FAS Auth Browser Extension", id: 6443941139 +mas "Bear", id: 1091189122 +mas "Bitwarden", id: 1352778147 +mas "Things 3", id: 904280696 +mas "Userscripts", id: 1463298887 +mas "uBlock Origin Lite", id: 6745342698 diff --git a/config/apps.conf b/config/apps.conf new file mode 100644 index 0000000..7c2b2ff --- /dev/null +++ b/config/apps.conf @@ -0,0 +1,106 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# App Registry — single source of truth for GUI apps + App Store apps +# ═══════════════════════════════════════════════════════════════════════════════ +# Consumed by: custom_bins/app-picker (gum toggle TUI → generates config/Brewfile) +# Installed by: brew bundle --file=config/Brewfile (install.sh --apps) +# +# Format (pipe-delimited, one app per line): +# method | id | category | tier | default | name | description | auth +# +# method brew | cask | mas +# id formula name (brew) / cask token (cask) / numeric App Store id (mas) +# category text tasks editor llm cli meetings cloud search messaging +# productivity time voice vpn auth safari music misc antivirus security +# tier 1 = official-vendor auto-approve · 2 = mature OSS (review) · 3 = explicit approval +# (tier-3 ships default=false; toggle on deliberately) +# default true | false — initial toggle state in the picker +# name display name; for `mas` this is the exact App Store title in the Brewfile +# auth post-install setup the auth-setup checklist surfaces: +# none login apikey pair-phone safari-ext license alfred things-cloud +# +# SECURITY POLICY (see claude/rules/supply-chain-security.md): +# - Homebrew official casks + Mac App Store only. NEVER add a third-party tap here. +# - Prefer `mas` (sandboxed, Apple-reviewed) when an app ships full-featured on the +# App Store — that's the highest trust tier ("MAS-first"). +# - Run `brew info ` / `mas info ` and verify vendor+homepage before adding. +# - Never use --no-quarantine; Gatekeeper/notarization must stay on. +# ═══════════════════════════════════════════════════════════════════════════════ + +# ─── text ─────────────────────────────────────────────────────────────────── +mas|1091189122|text|2|true|Bear|Markdown notes (App Store only; bearcli symlinked by deploy)|login +cask|notion|text|1|true|Notion|Notes, docs, and wiki|login + +# ─── task management ───────────────────────────────────────────────────────── +mas|904280696|tasks|2|true|Things 3|GTD task manager (App Store only)|things-cloud + +# ─── coding: editors ───────────────────────────────────────────────────────── +cask|cursor|editor|1|true|Cursor|AI-first code editor (config deployed)|login +cask|antigravity|editor|1|true|Antigravity|Google agentic IDE (config deployed)|login +cask|zed|editor|1|true|Zed|Fast collaborative editor (config deployed)|login + +# ─── coding: LLM desktop apps ──────────────────────────────────────────────── +cask|chatgpt|llm|1|true|ChatGPT|OpenAI desktop app|login +cask|claude|llm|1|true|Claude|Anthropic desktop app|login + +# ─── coding: terminal ──────────────────────────────────────────────────────── +cask|ghostty|cli|1|true|Ghostty|GPU-accelerated terminal (config deployed)|none + +# ─── meetings ──────────────────────────────────────────────────────────────── +cask|granola|meetings|2|true|Granola|AI meeting notes|login + +# ─── cloud storage ─────────────────────────────────────────────────────────── +cask|dropbox|cloud|1|true|Dropbox|File sync and storage|login +cask|google-drive|cloud|1|false|Google Drive|Google cloud storage (optional)|login + +# ─── search / launcher ─────────────────────────────────────────────────────── +cask|alfred|search|2|true|Alfred|Launcher + workflows (prefs sync via Dropbox)|alfred + +# ─── messaging ─────────────────────────────────────────────────────────────── +cask|slack|messaging|1|true|Slack|Team messaging|login +cask|readdle-spark|messaging|2|true|Spark|Email client (MAS build also full-featured)|login + +# ─── productivity ──────────────────────────────────────────────────────────── +cask|mouseless|productivity|2|true|Mouseless|Keyboard-driven mouse control (config deployed)|none +cask|popclip|productivity|2|true|PopClip|Text-selection actions (cask not MAS — MAS abandoned)|license + +# ─── time tracking ─────────────────────────────────────────────────────────── +cask|super-productivity|time|2|true|Super Productivity|Task + time tracker|none +brew|wakatime-cli|time|2|false|WakaTime CLI|Coding time tracker (API key via secrets)|apikey + +# ─── voice ─────────────────────────────────────────────────────────────────── +cask|voiceink|voice|2|true|VoiceInk|Local voice-to-text (downloads model on first run)|none + +# ─── vpn ───────────────────────────────────────────────────────────────────── +cask|nordvpn|vpn|2|true|NordVPN|VPN client (vpn deploy configures split tunnel)|login +cask|tailscale-app|vpn|1|true|Tailscale|Mesh VPN (vpn deploy configures routing)|login + +# ─── auth / passwords / 2FA ────────────────────────────────────────────────── +# Bitwarden via MAS: the Safari extension ships only in the App Store build, which +# also covers the desktop app — so mas covers both. (cask `bitwarden` = desktop only.) +mas|1352778147|auth|1|true|Bitwarden|Password manager (App Store build incl. Safari ext)|login +mas|6443941139|auth|2|true|2FAS Auth Browser Extension|2FA browser extension (pairs with phone)|pair-phone + +# ─── safari extensions (install via mas; ENABLE manually in Safari settings) ── +mas|6745342698|safari|2|true|uBlock Origin Lite|Content blocker (enable in Safari)|safari-ext +mas|1463298887|safari|2|true|Userscripts|Userscript manager (enable in Safari)|safari-ext + +# ─── music ─────────────────────────────────────────────────────────────────── +cask|spotify|music|1|true|Spotify|Music streaming|login + +# ─── misc utilities ────────────────────────────────────────────────────────── +cask|aldente|misc|2|true|AlDente|Battery charge limiter|none +cask|finicky|misc|2|true|Finicky|Browser routing (config deployed)|none +cask|appcleaner|misc|2|true|AppCleaner|Thorough app uninstaller|none +cask|cleanshot|misc|1|true|CleanShot X|Screenshot + screen recording|license +cask|stats|misc|2|true|Stats|Menu-bar system monitor|none +cask|keyboardcleantool|misc|2|true|KeyboardCleanTool|Disable keyboard for cleaning|none +cask|beardedspice|misc|2|true|BeardedSpice|Media keys for web players|none + +# ─── antivirus ─────────────────────────────────────────────────────────────── +# Optional, default OFF. Trellix (university-managed) is NOT here — installed via uni. +# Do NOT run Malwarebytes real-time protection alongside Trellix (on-demand scan is fine). +cask|malwarebytes|antivirus|2|false|Malwarebytes|On-demand malware scanner (optional)|login + +# ─── security: runtime defense ─────────────────────────────────────────────── +# Optional, default OFF (prompts frequently). Objective-See outbound firewall. +cask|lulu|security|2|false|LuLu|Outbound firewall — catches apps phoning home|none diff --git a/custom_bins/app-picker b/custom_bins/app-picker new file mode 100755 index 0000000..a66d725 --- /dev/null +++ b/custom_bins/app-picker @@ -0,0 +1,135 @@ +#!/usr/bin/env zsh +# ═══════════════════════════════════════════════════════════════════════════════ +# app-picker — browse the app registry, toggle apps, generate a Brewfile +# ═══════════════════════════════════════════════════════════════════════════════ +# Reads: config/apps.conf (method|id|category|tier|default|name|description|auth) +# Writes: config/Brewfile (brew/cask/mas entries for `brew bundle`) +# +# Usage: +# app-picker # gum TUI: space=toggle, enter=confirm → writes Brewfile +# app-picker --defaults # no TUI; select registry defaults, write Brewfile +# app-picker --dry-run # print the Brewfile to stdout, don't write +# app-picker --conf PATH # registry path (default: /config/apps.conf) +# app-picker --file PATH # Brewfile output path (default: /config/Brewfile) +# +# Trust policy: registry is official casks + Mac App Store only (no third-party taps). +# ═══════════════════════════════════════════════════════════════════════════════ +set -euo pipefail + +# Resolve dotfiles dir (this script lives in custom_bins/) +SCRIPT_DIR="${0:A:h}" +DOT_DIR="${DOT_DIR:-${SCRIPT_DIR:h}}" + +CONF="$DOT_DIR/config/apps.conf" +BREWFILE="$DOT_DIR/config/Brewfile" +MODE="tui" # tui | defaults +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --defaults) MODE="defaults"; shift ;; + --dry-run) DRY_RUN=true; shift ;; + --conf) CONF="$2"; shift 2 ;; + --file) BREWFILE="$2"; shift 2 ;; + -h|--help) sed -n '2,18p' "$0"; exit 0 ;; + *) echo "app-picker: unknown arg '$1'" >&2; exit 2 ;; + esac +done + +[[ -f "$CONF" ]] || { echo "app-picker: registry not found: $CONF" >&2; exit 1; } + +# ─── Parse registry ────────────────────────────────────────────────────────── +# Arrays are index-aligned. zsh arrays are 1-based. +typeset -a A_METHOD A_ID A_CAT A_TIER A_DEFAULT A_NAME A_DESC A_AUTH +while IFS='|' read -r method id cat tier def name desc auth; do + [[ -z "${method// }" || "${method[1]}" == "#" ]] && continue + A_METHOD+=("${method// }"); A_ID+=("$id"); A_CAT+=("${cat// }") + A_TIER+=("${tier// }"); A_DEFAULT+=("${def// }") + A_NAME+=("$name"); A_DESC+=("$desc"); A_AUTH+=("${auth// }") +done < "$CONF" + +local n=${#A_METHOD} +(( n > 0 )) || { echo "app-picker: no apps parsed from $CONF" >&2; exit 1; } + +# ─── Build display rows (one per app) + default-selected set ────────────────── +# Row format the TUI shows / we parse back: "\t[cat·Tn] name — desc" +typeset -a rows selected +local i +for (( i=1; i<=n; i++ )); do + local label="${i} [${A_CAT[i]}·T${A_TIER[i]}] ${A_NAME[i]} — ${A_DESC[i]}" + rows+=("$label") + [[ "${A_DEFAULT[i]}" == "true" ]] && selected+=("$label") +done + +# ─── Choose: TUI (gum) or defaults ─────────────────────────────────────────── +# Result is the chosen subset of $rows, newline-separated. +local chosen="" +if [[ "$MODE" == "defaults" ]] || [[ "${NON_INTERACTIVE:-false}" == "true" ]] \ + || ! [[ -t 0 ]] || ! command -v gum &>/dev/null; then + chosen="${(F)selected}" +else + local sel_csv="${(j:,:)selected}" + local height=$(( n + 2 )) + local term_h; term_h=$(tput lines 2>/dev/null || echo 40) + (( height > term_h - 4 )) && height=$(( term_h - 4 )) + chosen=$(gum choose --no-limit --height "$height" \ + --header "Select apps to install (space=toggle, enter=confirm):" \ + --selected "$sel_csv" -- "$rows[@]") || { echo "Cancelled — Brewfile unchanged." >&2; exit 0; } +fi + +# ─── Map chosen rows back to indices ───────────────────────────────────────── +typeset -a pick_idx +local line +while IFS= read -r line; do + [[ -z "$line" ]] && continue + pick_idx+=("${line%% *}") # leading field before the tab = index +done <<< "$chosen" + +(( ${#pick_idx} > 0 )) || { echo "Nothing selected — Brewfile unchanged." >&2; exit 0; } + +# ─── Generate Brewfile ─────────────────────────────────────────────────────── +emit_brewfile() { + local has_mas=false idx + for idx in "${pick_idx[@]}"; do + [[ "${A_METHOD[idx]}" == "mas" ]] && { has_mas=true; break; } + done + + print -r -- "# Brewfile — GENERATED by app-picker from config/apps.conf. Do not edit by hand." + print -r -- "# Regenerate: app-picker (or app-picker --defaults)" + print -r -- "# Install: brew bundle --file=config/Brewfile" + print -r -- "# Policy: official casks + Mac App Store only; never --no-quarantine." + print -r -- "" + + if [[ "$has_mas" == "true" ]]; then + print -r -- "# mas-cli drives Mac App Store installs (must be signed into the App Store)." + print -r -- 'brew "mas"' + print -r -- "" + fi + + # brew formulae + local out + out=$(for idx in "${pick_idx[@]}"; do + if [[ "${A_METHOD[idx]}" == "brew" ]]; then print -r -- "brew \"${A_ID[idx]}\""; fi + done | sort) + [[ -n "$out" ]] && { print -r -- "# ── CLI formulae ──"; print -r -- "$out"; print -r -- ""; } + + # casks + out=$(for idx in "${pick_idx[@]}"; do + if [[ "${A_METHOD[idx]}" == "cask" ]]; then print -r -- "cask \"${A_ID[idx]}\""; fi + done | sort) + [[ -n "$out" ]] && { print -r -- "# ── Casks (official Homebrew casks) ──"; print -r -- "$out"; print -r -- ""; } + + # mas + out=$(for idx in "${pick_idx[@]}"; do + if [[ "${A_METHOD[idx]}" == "mas" ]]; then print -r -- "mas \"${A_NAME[idx]}\", id: ${A_ID[idx]}"; fi + done | sort) + [[ -n "$out" ]] && { print -r -- "# ── Mac App Store (sandboxed, Apple-reviewed) ──"; print -r -- "$out"; } +} + +if [[ "$DRY_RUN" == "true" ]]; then + emit_brewfile +else + emit_brewfile > "$BREWFILE" + echo "✓ Wrote ${#pick_idx} apps to $BREWFILE" + echo " Install with: brew bundle --file=$BREWFILE" +fi diff --git a/scripts/setup/auth-setup b/scripts/setup/auth-setup new file mode 100755 index 0000000..57f8002 --- /dev/null +++ b/scripts/setup/auth-setup @@ -0,0 +1,105 @@ +#!/usr/bin/env zsh +# ═══════════════════════════════════════════════════════════════════════════════ +# auth-setup — post-install login/setup checklist + signature verification +# ═══════════════════════════════════════════════════════════════════════════════ +# Run AFTER `brew bundle`. macOS only. Reads config/apps.conf for per-app auth notes. +# +# Usage: +# auth-setup # interactive checklist (auth steps) + signature verify +# auth-setup --verify # ONLY run the codesign/spctl signature audit +# auth-setup --checklist # ONLY print the auth/login checklist +# +# Nothing here stores secrets. API-key apps are pointed at the existing secrets +# flow (setup-envrc / with-secrets) — never plaintext. +# ═══════════════════════════════════════════════════════════════════════════════ +set -uo pipefail + +SCRIPT_DIR="${0:A:h}" +DOT_DIR="${DOT_DIR:-${SCRIPT_DIR:h:h}}" +CONF="$DOT_DIR/config/apps.conf" + +DO_CHECKLIST=true +DO_VERIFY=true +case "${1:-}" in + --verify) DO_CHECKLIST=false ;; + --checklist) DO_VERIFY=false ;; + -h|--help) sed -n '2,16p' "$0"; exit 0 ;; +esac + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "auth-setup: macOS only (uses spctl/codesign + open)." >&2 + exit 1 +fi +[[ -f "$CONF" ]] || { echo "auth-setup: registry not found: $CONF" >&2; exit 1; } + +hdr() { print -P "%F{cyan}── $* ──%f"; } + +# Human-readable instruction per auth type. +auth_hint() { + case "$1" in + login) echo "Open and sign in." ;; + apikey) echo "API key — add via 'setup-envrc' in the relevant repo (no plaintext)." ;; + pair-phone) echo "Pair with the phone app (scan QR in the desktop/extension)." ;; + safari-ext) echo "Enable in Safari → Settings → Extensions, then grant site access." ;; + license) echo "Enter your license key (paid app)." ;; + alfred) echo "Set sync folder to Dropbox (Prefs → Advanced → Set sync folder), apply Powerpack license." ;; + things-cloud) echo "Sign into Things Cloud to sync across devices." ;; + *) echo "" ;; + esac +} + +# ─── Checklist ─────────────────────────────────────────────────────────────── +print_checklist() { + hdr "Post-install setup checklist" + echo "Apps that need a login or manual step. (GUI logins can't be automated.)" + echo "" + local method id cat tier def name desc auth + while IFS='|' read -r method id cat tier def name desc auth; do + [[ -z "${method// }" || "${method[1]}" == "#" ]] && continue + auth="${auth// }" + [[ -z "$auth" || "$auth" == "none" ]] && continue + printf ' [ ] %-32s %s\n' "$name" "$(auth_hint "$auth")" + done < "$CONF" + + echo "" + hdr "Reminders" + cat <<'EOF' + [ ] App Store — SIGN IN via the App Store GUI first: `mas` can't sign in + (Apple removed the CLI API). A never-acquired free app may + need a one-time "Get" click before `mas install` works. + [ ] git / gh — `gh auth login` (SSH + identity also sync via gist) + [ ] Safari exts — uBlock Origin Lite, Userscripts, Bitwarden, 2FAS: ENABLE in Safari + [ ] Trellix — UNIVERSITY-MANAGED: install/update via your uni, NOT Homebrew. + Do not also run Malwarebytes real-time (on-demand scan is fine). + [ ] Alfred — point sync folder at Dropbox so workflows/prefs follow you +EOF +} + +# ─── Signature / notarization audit (§6.2) ─────────────────────────────────── +# Eyeballing installers is theatre; THIS is the real check — is each app signed +# by a Developer ID and notarized by Apple? +verify_signatures() { + hdr "Signature + notarization audit (/Applications)" + echo "Gatekeeper assessment per installed app. ✓ accepted = signed + notarized." + echo "" + local app appname result + for app in /Applications/*.app; do + [[ -d "$app" ]] || continue + appname="${app:t:r}" + if spctl --assess --type execute "$app" &>/dev/null; then + printf ' %s %s\n' "✓" "$appname" + else + # Not accepted by Gatekeeper — surface the authority so you can judge it. + local authority + authority=$(codesign -dv --verbose=2 "$app" 2>&1 | grep -m1 '^Authority=' | cut -d= -f2-) + printf ' %s %-30s %s\n' "⚠️ " "$appname" "${authority:-unsigned / no Developer ID}" + fi + done + echo "" + echo "⚠️ = not Gatekeeper-accepted: unsigned, ad-hoc, or self-distributed." + echo " Mac App Store + notarized casks should all show ✓. Investigate any ⚠️." +} + +[[ "$DO_CHECKLIST" == "true" ]] && print_checklist +[[ "$DO_CHECKLIST" == "true" && "$DO_VERIFY" == "true" ]] && echo "" +[[ "$DO_VERIFY" == "true" ]] && verify_signatures From 3747363b5a965dc138324493cb509ed0d5dafca2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 17:14:57 +0000 Subject: [PATCH 06/13] Wire --apps Brewfile install; prune zerobrew + Coven; harden uv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install.sh: new --apps block (macOS) — bootstrap gum, run app-picker, brew bundle config/Brewfile; folds in Finicky. Points to auth-setup afterward. - config.sh: add 'apps' to INSTALL_REGISTRY (replaces standalone 'finicky' install component); drop INSTALL_FINICKY from server profile - Prune zerobrew (P1): remove install block, zb alias wrapper, experimental.yaml entry - Prune Coven + Crazytieguy/tap (P3): third-party tap violates no-new-taps policy (alignment-hive plugin marketplace left intact — separate, not a brew tap) - Harden uv install: prefer official brew formula on macOS over curl|bash (sha-pinned) --- config.sh | 6 ++--- config/aliases.sh | 15 ----------- config/experimental.yaml | 8 ------ install.sh | 56 +++++++++++++++++++++++++++------------- 4 files changed, 41 insertions(+), 44 deletions(-) diff --git a/config.sh b/config.sh index 1de19cc..6c92589 100644 --- a/config.sh +++ b/config.sh @@ -36,9 +36,9 @@ INSTALL_REGISTRY=( "ai-tools|Claude Code, Gemini CLI, Codex CLI|all|true" "extras|hyperfine, gitui, code2prompt, terminal-notifier|all|true" "cleanup|Automatic cleanup (macOS only)|all|true" - "experimental|ty type checker, zerobrew|all|true" + "experimental|ty type checker, zotero MCP|all|true" "macos-settings|macOS system defaults (Dock, Finder, keyboard)|macos|true" - "finicky|Finicky browser routing|macos|true" + "apps|GUI + App Store apps via Brewfile (picker TUI)|macos|true" "docker|Docker engine + compose|linux|true" "pueue|Pueue job scheduler + pueued daemon|linux|true" "create-user|Create non-root dev user|linux|true" @@ -229,7 +229,7 @@ apply_profile() { INSTALL_DOCKER=false INSTALL_EXTRAS=false INSTALL_MACOS_SETTINGS=false - INSTALL_FINICKY=false + INSTALL_APPS=false DEPLOY_EDITOR=false DEPLOY_SERENA=false DEPLOY_GHOSTTY=false diff --git a/config/aliases.sh b/config/aliases.sh index 29f8ee9..22e04ad 100644 --- a/config/aliases.sh +++ b/config/aliases.sh @@ -1322,21 +1322,6 @@ alias ai-update='update-ai-tools' # Detects: brew (macOS), apt/dnf/pacman (Linux) alias pkg-update='update-packages' -# zerobrew: faster Homebrew client (use zb for interactive installs, brew for scripts) -# `zb install` falls back to `brew install` on failure (zerobrew doesn't handle casks) -if command -v zb &>/dev/null; then - zb() { - if [[ "$1" == "install" ]]; then - shift - command zb install "$@" || { echo "→ zb failed, falling back to brew install" >&2; brew install "$@"; } - else - command zb "$@" - fi - } - alias zbi='zb install' - alias zbu='zb uninstall' -fi - # Auto-agent guard controls alias auto-guard='auto-agent-guardctl status' alias auto-approve='auto-agent-guardctl approve' diff --git a/config/experimental.yaml b/config/experimental.yaml index cbb2b97..a162129 100644 --- a/config/experimental.yaml +++ b/config/experimental.yaml @@ -28,11 +28,3 @@ settings_keys: [] reason: "Rust-based Python type checker, 10-60x faster than mypy/pyright. Alpha — fall back to pyright if gaps block." status: trial - -- name: zerobrew - added: 2026-04-01 - review_by: 2026-07-01 - installed_via: "curl https://zerobrew.rs/install | bash" - settings_keys: [] - reason: "Fast Rust-based Homebrew client. Same UX, faster." - status: trial diff --git a/install.sh b/install.sh index 817f02a..f134b37 100755 --- a/install.sh +++ b/install.sh @@ -55,7 +55,8 @@ COMPONENTS: --cleanup Enable automatic cleanup (macOS only) --docker Enable Docker installation (Linux only) --pueue Enable Pueue job scheduler (Linux only) - --experimental Enable experimental features (ty type checker, zerobrew) + --experimental Enable experimental features (ty type checker, zotero MCP) + --apps Install GUI + App Store apps via Brewfile picker (macOS) --create-user Create non-root dev user (Linux only) --no- Disable a component (e.g., --no-ai-tools) --force-reinstall Reinstall tools even if present @@ -155,7 +156,13 @@ fi if ! is_installed uv; then log_info "Installing uv..." - curl -LsSf https://astral.sh/uv/install.sh | sh + # Prefer the official Homebrew formula on macOS: sha-pinned + reviewed, unlike + # the upstream curl|bash installer (authentic over HTTPS but not tamper-evident). + if is_macos && cmd_exists brew; then + brew_install uv + else + curl -LsSf https://astral.sh/uv/install.sh | sh + fi fi fi # INSTALL_CORE @@ -250,12 +257,6 @@ if [[ "$INSTALL_AI_TOOLS" == "true" ]]; then "codex|install_codex_cli" fi - # Coven (macOS only, lightweight Claude interface) - if is_macos && ! is_installed coven; then - log_info "Installing Coven..." - brew tap Crazytieguy/tap 2>/dev/null && brew_install coven || log_warning "Coven installation failed" - fi - # MCP servers (sequential — unclear if concurrent-safe) if cmd_exists claude; then log_info "Configuring MCP servers..." @@ -385,12 +386,6 @@ if [[ "$INSTALL_EXPERIMENTAL" == "true" ]]; then uv tool install ty 2>/dev/null || log_warning "ty installation failed" fi - # zerobrew: fast Rust-based Homebrew client (installs to /opt/zerobrew, won't touch /opt/homebrew) - if ! is_installed zb; then - log_info "Installing zerobrew (experimental Homebrew alternative)..." - curl --proto '=https' --tlsv1.2 -fsSL https://zerobrew.rs/install | bash 2>/dev/null || log_warning "zerobrew installation failed" - fi - # zotero-mcp-server: Zotero MCP for citation management (see config/experimental.yaml) if ! is_installed zotero-mcp && cmd_exists uv; then log_info "Installing zotero-mcp-server (citation library management)..." @@ -413,11 +408,36 @@ if [[ "$INSTALL_MACOS_SETTINGS" == "true" ]] && is_macos && [[ -f "$DOT_DIR/conf "$DOT_DIR/config/macos_settings.sh" || log_warning "macOS settings had some errors" fi -# ─── Finicky (macOS) ────────────────────────────────────────────────────────── +# ─── GUI + App Store Apps (macOS, via Brewfile) ─────────────────────────────── +# Pick apps in a TUI (app-picker reads config/apps.conf → generates config/Brewfile), +# then install via `brew bundle`. Official casks + Mac App Store only; never +# --no-quarantine (Gatekeeper/notarization must stay on). Finicky now lives here too. + +if [[ "$INSTALL_APPS" == "true" ]] && is_macos; then + log_section "INSTALLING APPS (Brewfile) 📦" + + if ! cmd_exists brew; then + log_warning "Homebrew required for apps — skipping" + else + # gum drives the picker; bootstrap it (tiny formula) if missing. + cmd_exists gum || brew_install gum + + brewfile="$DOT_DIR/config/Brewfile" + if [[ "${NON_INTERACTIVE:-false}" == "true" ]] || ! [[ -t 0 ]]; then + log_info "Non-interactive: using committed Brewfile (run 'app-picker' to customise)" + else + # Interactive: let the user toggle apps, regenerating the Brewfile. + "$DOT_DIR/custom_bins/app-picker" || log_warning "app-picker cancelled — using existing Brewfile" + fi -if [[ "$INSTALL_FINICKY" == "true" ]] && is_macos && ! is_cask_installed finicky; then - log_info "Installing Finicky..." - brew_install finicky true + if [[ -f "$brewfile" ]]; then + log_info "Installing apps from Brewfile (this can take a while)..." + brew bundle --file="$brewfile" || log_warning "Some Brewfile entries failed (mas needs App Store sign-in)" + log_info "Next: run scripts/setup/auth-setup for logins + signature audit" + else + log_warning "No Brewfile at $brewfile — run 'app-picker' first" + fi + fi fi # ─── Done ───────────────────────────────────────────────────────────────────── From 30cd631d2dffb4f66a4fe4b431cecd10a66bb49f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 17:15:36 +0000 Subject: [PATCH 07/13] Document app/Brewfile + curl|bash policy in supply-chain rule and CLAUDE.md - supply-chain-security.md: GUI apps & Brewfile section (casks+mas only, no taps, MAS-first, never --no-quarantine, brew info before adding); curl|bash hardening (prefer formula > verify checksum > blind pipe; glance != integrity control) - CLAUDE.md: 'Install/manage Mac apps' quick-reference row --- CLAUDE.md | 1 + claude/rules/supply-chain-security.md | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 6987830..22b59e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,6 +28,7 @@ If you're an AI agent (Claude Code, Codex, etc.) working in this repo, read this | Add a new alias | `config/aliases.sh` (or `aliases_.sh` for env-specific) | | Add a deploy component | Create `deploy_X()` in `deploy.sh` — see [Adding New Features](#adding-new-features) | | Add a custom binary | Drop it in `custom_bins/` (already on PATH); `chmod +x` | +| Install/manage Mac apps | Add a line to `config/apps.conf` → run `app-picker` (gum TUI) → `brew bundle --file=config/Brewfile`. Official casks + `mas` only, **no third-party taps**. Then `scripts/setup/auth-setup` | | Add an encrypted secret | `secrets-edit` (interactive dotenv editor) | | Run an experiment with resource caps | `jexp uv run python -m ...` (Linux: needs pueue + systemd user session) | | Commit / commit + push + PR | `/commit` skill or `/commit-push-sync` | diff --git a/claude/rules/supply-chain-security.md b/claude/rules/supply-chain-security.md index 2378d32..1c55256 100644 --- a/claude/rules/supply-chain-security.md +++ b/claude/rules/supply-chain-security.md @@ -50,6 +50,32 @@ All package managers are configured with a **7-day quarantine** (`min-release-ag - Skip hash verification for production Python dependencies - Bypass min-release-age quarantine without explicit user approval +## GUI Apps & Brewfile (macOS) + +Apps live in `config/apps.conf` (registry) → `config/Brewfile` (generated by `app-picker`). + +- **Homebrew official casks + Mac App Store (`mas`) ONLY.** NEVER add a third-party tap + to `apps.conf`, install.sh, or a Brewfile without explicit user approval. +- **Prefer `mas`** (sandboxed, Apple-reviewed) when an app ships full-featured on the + App Store — highest trust tier ("MAS-first"). Use a cask when MAS is crippled/absent. +- Before adding any app: run `brew info ` / `mas info `, verify vendor + homepage. +- **Never `--no-quarantine`.** Gatekeeper + notarization must stay enabled; that's the + defense against malicious casks (brew also verifies a pinned sha256 on download). +- Tier in `apps.conf`: 1 = official vendor auto-approve · 2 = mature OSS (review) · + 3 = explicit approval (ships `default=false`). + +## curl|bash Installers + +Official-page `curl … | sh` gives **authenticity** (HTTPS proves the domain) but NOT +**integrity** (runs whatever's live, unpinned, unreviewed). Prefer, best→worst: +1. Official Homebrew **formula** if one exists (`uv`, `rustup-init`, `bun`) — vendor's + artifact + sha pin + reviewed PR + reproducible. +2. No formula → `curl -o` a versioned URL and **verify the vendor checksum/signature**. +3. Blind `curl … | sh` only as last resort, HTTPS-to-official-domain only. + +Eyeballing the script ("glance at it") is a smell test for gross tampering, NOT an +integrity control — don't treat it as a safeguard. + ## Secrets Awareness - API keys are scoped per-project via direnv `.envrc`, NOT globally exported From 7291a5beb15c5feb0e913e39f5240a1431da984c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 17:16:31 +0000 Subject: [PATCH 08/13] Clarify in plan: manual script inspection is a smell test, not integrity control --- plans/2026-06-16-mac-app-setup-brewfile.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plans/2026-06-16-mac-app-setup-brewfile.md b/plans/2026-06-16-mac-app-setup-brewfile.md index 6dbcdfb..ec996ef 100644 --- a/plans/2026-06-16-mac-app-setup-brewfile.md +++ b/plans/2026-06-16-mac-app-setup-brewfile.md @@ -167,7 +167,8 @@ Trust tiers gate *what* we install; this section gates *integrity* (is the bytes 3. **LuLu** (Objective-See, free OSS outbound firewall) — optional cask, **default OFF** (prompts a lot). Fills the runtime/egress gap: catches a signed-but-compromised app phoning home. Document KnockKnock (persistence enumeration) + BlockBlock (persistence alerts) as further optional Objective-See tools. 4. **Harden `curl|bash` installers** — resolves "is official-page curl|bash ok?": - Official page gives **authenticity** (HTTPS cert proves the domain) but NOT **integrity-over-time** (runs whatever's live, unseen), **pinning** (no agreed sha → tamper passes), or **reproducibility**. - - **Rule, best→worst:** (a) use the official **brew formula** if it exists — `uv`, `rustup-init`, `bun` all do; you get the vendor's artifact + sha pin + reproducible re-run. (b) No formula → `curl -o` the script, verify vendor checksum/signature if published, inspect, then run — never blind-pipe. (c) blind `curl … | sh` only as last resort, HTTPS-to-official-domain only. + - **Rule, best→worst:** (a) use the official **brew formula** if it exists — `uv`, `rustup-init`, `bun` all do; you get the vendor's artifact + sha pin + reviewed PR + reproducible re-run. (b) No formula → `curl -o` the script to a versioned URL and **verify the vendor's published checksum/signature** if they offer one (this is the actual tamper-evidence). (c) blind `curl … | sh` only as last resort, HTTPS-to-official-domain only. + - **Note on manual inspection:** eyeballing the script ("glance at it") is a low-effort smell test for *gross* tampering (second payloads, surprise `sudo`), NOT an integrity control — a competent attacker defeats it, and you're reading the installer not the binary it fetches. Don't treat it as a safeguard; the safeguards are (a)/(b). - Migrate existing blind pipes in `install.sh` (uv, rust) to brew formulae / fetch-verify-run. --- From 14cd14b08f8a9e1e0c291022d6a3f0c11feab7c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 18:33:02 +0000 Subject: [PATCH 09/13] =?UTF-8?q?Migrate=20Gemini=20CLI=20=E2=86=92=20Anti?= =?UTF-8?q?gravity=20CLI=20(official=20successor)=20+=20OpenCode;=20add=20?= =?UTF-8?q?FineTune;=20cull=20iTerm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemini CLI consumer access ends 2026-06-18; Google's official successor is Antigravity CLI (agy). Wire both the official successor and OpenCode (OSS, multi-provider) into the ai-tools component. - helpers.sh: install_gemini_cli → install_opencode (core formula) + install_antigravity_cli (cask antigravity-cli; Linux = manual per curl|bash policy) - install.sh / config.sh / update-ai-tools / ai-check: swap Gemini → OpenCode + Antigravity - auto_commit_worker + ai_automation: backend order codex,gemini → codex,opencode - scripts: sync_claude_to_gemini.sh → sync_claude_to_antigravity.sh (skills → ~/.gemini/antigravity-cli/skills; AGENTS.md; permission sync flagged untested) - apps.conf: add FineTune (audio, OSS, default-OFF per young/single-maintainer rule) - supply-chain-security.md: add two-gate modernity/adoption selection rule - cull iTerm leftovers (itermcolors); remove GEMINI.md + gemini/ - README: Gemini CLI section → Antigravity CLI + OpenCode https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- GEMINI.md | 6 - README.md | 18 +- claude/hooks/auto_commit_worker.sh | 9 +- claude/rules/supply-chain-security.md | 18 ++ config.sh | 4 +- config/ai_automation.sh | 2 +- config/aliases.sh | 2 +- config/apps.conf | 8 +- custom_bins/update-ai-tools | 8 +- gemini/GEMINI.md | 242 ------------------ install.sh | 12 +- iterm/onedark.itermcolors | 303 ----------------------- iterm/onedarker.itermcolors | 344 -------------------------- scripts/cleanup/setup_ai_update.sh | 4 +- scripts/cloud/setup.sh | 2 +- scripts/shared/helpers.sh | 28 ++- scripts/sync_claude_to_antigravity.sh | 64 +++++ scripts/sync_claude_to_gemini.sh | 121 --------- 18 files changed, 142 insertions(+), 1053 deletions(-) delete mode 100644 GEMINI.md delete mode 100644 gemini/GEMINI.md delete mode 100644 iterm/onedark.itermcolors delete mode 100644 iterm/onedarker.itermcolors create mode 100755 scripts/sync_claude_to_antigravity.sh delete mode 100755 scripts/sync_claude_to_gemini.sh diff --git a/GEMINI.md b/GEMINI.md deleted file mode 100644 index dc293dc..0000000 --- a/GEMINI.md +++ /dev/null @@ -1,6 +0,0 @@ -# GEMINI.md - -This project uses [CLAUDE.md](CLAUDE.md) as the single source of truth for project-specific guidelines, architecture, and workflows. -Please refer to that file for all development patterns, conventions, and command usages. - -For global Gemini agent guidelines and AI safety protocols, refer to [gemini/GEMINI.md](gemini/GEMINI.md). diff --git a/README.md b/README.md index 4bc990e..bbde43f 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ For cloud environments (RunPod, Hetzner, Lambda Labs, etc): - [AI Assistants](#ai-assistants) - [Claude Code](#claude-code-primary-ai-assistant) - [Codex CLI](#codex-cli-openai) - - [Gemini CLI](#gemini-cli-google) + - [Antigravity CLI + OpenCode](#antigravity-cli-google--opencode) - [Terminal & Shell](#terminal--shell) - [Ghostty](#ghostty-terminal-emulator) - [Powerlevel10k Prompt](#powerlevel10k-prompt) @@ -273,22 +273,22 @@ claude-tools context --list # Show active plugins and available prof The configuration follows the same research discipline as Claude Code but adapted for Codex's execution model. -### Gemini CLI (Google) +### Antigravity CLI (Google) + OpenCode -[Gemini CLI](https://github.com/google-gemini/gemini-cli) can sync with Claude Code configurations: +Gemini CLI was retired by Google on **2026-06-18**; [Antigravity CLI](https://antigravity.google/docs/cli-features) (`agy`) is its official successor. [OpenCode](https://opencode.ai) is installed alongside as a model-agnostic OSS option. Both are installed by the `ai-tools` component. + +Antigravity CLI can sync with Claude Code skills: ```bash -./scripts/sync_claude_to_gemini.sh # Syncs skills/agents/permissions +./scripts/sync_claude_to_antigravity.sh # Symlinks Claude skills into agy ``` **What it does:** -- Symlinks Claude Code skills to `~/.gemini/skills/` -- Converts Claude agents to Gemini skill format -- Syncs permissions from `.claude/settings.json` to Gemini policies -- Creates `GEMINI.md` pointer to CLAUDE.md +- Symlinks Claude Code skills to `~/.gemini/antigravity-cli/skills/` +- Project instructions come from `AGENTS.md` (Antigravity reads it natively) -**Note:** Gemini CLI uses a different skills format. The sync script adapts Claude's configuration but some features may not translate directly. +**Note:** Antigravity CLI is closed-source and brand-new; its skills/permissions schema differs from Claude's. The skills sync is adapted but untested end-to-end — permission sync is not yet ported (see the script header). ## Terminal & Shell diff --git a/claude/hooks/auto_commit_worker.sh b/claude/hooks/auto_commit_worker.sh index eaae5c2..de3ae9b 100755 --- a/claude/hooks/auto_commit_worker.sh +++ b/claude/hooks/auto_commit_worker.sh @@ -63,7 +63,7 @@ fi : "${AUTO_AGENT_APPROVAL_FILE:=$HOME/.claude/flags/auto-agent-approved-until}" : "${AUTO_AGENT_STATE_DIR:=$HOME/.claude/state}" : "${AUTO_AGENT_LOG_DIR:=$HOME/.claude/logs/auto-commit}" -: "${AUTO_COMMIT_BACKEND_ORDER:=codex,gemini}" +: "${AUTO_COMMIT_BACKEND_ORDER:=codex,opencode}" : "${AUTO_COMMIT_ENABLE_CLAUDE_FALLBACK:=0}" : "${AUTO_COMMIT_DRY_RUN:=0}" : "${AUTO_AGENT_EXCLUDE_REGEX:=^\\.claude/worktrees/}" @@ -246,10 +246,9 @@ run_backend() { command -v codex >/dev/null 2>&1 || return 1 run_with_timeout 240 codex -a never -s workspace-write exec --cd "$REPO_ROOT" --skip-git-repo-check "$prompt" >> "$log_file" 2>&1 ;; - gemini) - command -v gemini >/dev/null 2>&1 || return 1 - run_with_timeout 240 gemini \ - -p "$prompt" --approval-mode yolo --output-format text >> "$log_file" 2>&1 + opencode) + command -v opencode >/dev/null 2>&1 || return 1 + run_with_timeout 240 opencode run "$prompt" >> "$log_file" 2>&1 ;; claude) [[ "${AUTO_COMMIT_ENABLE_CLAUDE_FALLBACK:-0}" == "1" ]] || return 1 diff --git a/claude/rules/supply-chain-security.md b/claude/rules/supply-chain-security.md index 1c55256..587ca02 100644 --- a/claude/rules/supply-chain-security.md +++ b/claude/rules/supply-chain-security.md @@ -50,6 +50,24 @@ All package managers are configured with a **7-day quarantine** (`min-release-ag - Skip hash verification for production Python dependencies - Bypass min-release-age quarantine without explicit user approval +## Tool Selection: Security Floor, then Adoption (two-gate) + +When choosing between tools, apply two gates in order: + +1. **Hard security floor (non-negotiable):** official core formula / cask / Mac App Store + only; **no third-party taps** without approval; notarization + quarantine on for casks; + `min-release-age` for language packages. A tool that fails the floor is out regardless + of popularity. +2. **Among options that clear the floor, prefer the more *modern / adopted* one** — GitHub + stars, monthly actives, release cadence, and HN/Reddit consensus. Higher adoption is + *also* a security positive (more eyes → faster CVE discovery), so this complements the + floor rather than fighting it. Don't default to a stale "boring" tool when a + well-adopted modern one clears the same floor. + +**Residual-risk case:** a tool that is *young AND single-maintainer AND not-yet-widely- +adopted* (e.g. FineTune). High stars only partially offset bus-factor risk — such tools may +be *added* but ship **default-OFF** (conscious opt-in), never auto-on. + ## GUI Apps & Brewfile (macOS) Apps live in `config/apps.conf` (registry) → `config/Brewfile` (generated by `app-picker`). diff --git a/config.sh b/config.sh index 6c92589..96ead57 100644 --- a/config.sh +++ b/config.sh @@ -33,7 +33,7 @@ INSTALL_REGISTRY=( "core|Core packages, CLI tools, gh, SOPS/age, uv|all|true" "zsh|ZSH + oh-my-zsh + powerlevel10k theme|all|true" "tmux|Terminal multiplexer|all|true" - "ai-tools|Claude Code, Gemini CLI, Codex CLI|all|true" + "ai-tools|Claude Code, Codex CLI, OpenCode, Antigravity CLI|all|true" "extras|hyperfine, gitui, code2prompt, terminal-notifier|all|true" "cleanup|Automatic cleanup (macOS only)|all|true" "experimental|ty type checker, zotero MCP|all|true" @@ -64,7 +64,7 @@ DEPLOY_REGISTRY=( "dep-audit|Weekly dependency audit (supply chain defense)|all|true" "cleanup|Auto-cleanup Downloads/Screenshots (macOS)|all|true" "claude-cleanup|Remove idle Claude sessions after 24h|all|true" - "ai-update|Daily auto-update: Claude, Gemini, Codex|all|true" + "ai-update|Daily auto-update: Claude, Codex, OpenCode|all|true" "mcp-sync|Daily shared MCP sync for Claude and Codex|all|true" "brew-update|Weekly package upgrade + cleanup|all|true" "claude-tools|Build claude-tools Rust binary|all|true" diff --git a/config/ai_automation.sh b/config/ai_automation.sh index cfcbc4d..2617826 100755 --- a/config/ai_automation.sh +++ b/config/ai_automation.sh @@ -35,7 +35,7 @@ # Auto-commit policy # Keep Claude fallback opt-in because it is usually the most expensive backend. -: "${AUTO_COMMIT_BACKEND_ORDER:=codex,gemini}" +: "${AUTO_COMMIT_BACKEND_ORDER:=codex,opencode}" : "${AUTO_COMMIT_ENABLE_CLAUDE_FALLBACK:=0}" : "${AUTO_COMMIT_DRY_RUN:=0}" : "${AUTO_COMMIT_USE_ASYNC:=1}" diff --git a/config/aliases.sh b/config/aliases.sh index 22e04ad..f72a19f 100644 --- a/config/aliases.sh +++ b/config/aliases.sh @@ -1215,7 +1215,7 @@ fi # AI CLI Tools # ------------------------------------------------------------------- # Health check for all AI CLI tools -alias ai-check='echo "Checking AI CLI tools..." && claude --version 2>/dev/null && gemini --version 2>/dev/null && codex --version 2>/dev/null' +alias ai-check='echo "Checking AI CLI tools..." && claude --version 2>/dev/null && codex --version 2>/dev/null && opencode --version 2>/dev/null' # Log sandbox denials for a command (macOS/Linux) codex-denials() { diff --git a/config/apps.conf b/config/apps.conf index 7c2b2ff..97cd4c2 100644 --- a/config/apps.conf +++ b/config/apps.conf @@ -10,7 +10,7 @@ # method brew | cask | mas # id formula name (brew) / cask token (cask) / numeric App Store id (mas) # category text tasks editor llm cli meetings cloud search messaging -# productivity time voice vpn auth safari music misc antivirus security +# productivity time voice audio vpn auth safari music misc antivirus security # tier 1 = official-vendor auto-approve · 2 = mature OSS (review) · 3 = explicit approval # (tier-3 ships default=false; toggle on deliberately) # default true | false — initial toggle state in the picker @@ -70,6 +70,12 @@ brew|wakatime-cli|time|2|false|WakaTime CLI|Coding time tracker (API key via sec # ─── voice ─────────────────────────────────────────────────────────────────── cask|voiceink|voice|2|true|VoiceInk|Local voice-to-text (downloads model on first run)|none +# ─── audio device management ───────────────────────────────────────────────── +# FineTune: per-app volume + multi-device output/routing + EQ (free OSS SoundSource alt). +# Default OFF: newer + single-maintainer project — adoption is high but bus-factor +# risk remains, so it's a conscious opt-in (see supply-chain-security.md modernity rule). +cask|finetune|audio|2|false|FineTune|Per-app volume + audio device routing + EQ (OSS)|none + # ─── vpn ───────────────────────────────────────────────────────────────────── cask|nordvpn|vpn|2|true|NordVPN|VPN client (vpn deploy configures split tunnel)|login cask|tailscale-app|vpn|1|true|Tailscale|Mesh VPN (vpn deploy configures routing)|login diff --git a/custom_bins/update-ai-tools b/custom_bins/update-ai-tools index 840ed30..8007c4f 100755 --- a/custom_bins/update-ai-tools +++ b/custom_bins/update-ai-tools @@ -2,7 +2,7 @@ # ═══════════════════════════════════════════════════════════════════════════════ # AI CLI Tools Auto-Update # ═══════════════════════════════════════════════════════════════════════════════ -# Updates Claude Code, Gemini CLI, and Codex CLI using the correct method +# Updates Claude Code, Codex CLI, and OpenCode using the correct method # per tool (brew on macOS, bun on Linux). Designed to run from launchd/cron. # # Usage: @@ -37,7 +37,7 @@ while [[ $# -gt 0 ]]; do -h|--help) echo "Usage: update-ai-tools [--dry-run]" echo "" - echo "Updates Claude Code, Gemini CLI, and Codex CLI." + echo "Updates Claude Code, Codex CLI, and OpenCode." echo "Uses brew on macOS and bun on Linux." echo "" echo "Options:" @@ -159,8 +159,8 @@ log "PATH: $PATH" set +e update_claude -update_tool "gemini" "gemini-cli" "@google/gemini-cli" -update_tool "codex" "codex" "@openai/codex" +update_tool "codex" "codex" "@openai/codex" +update_tool "opencode" "opencode" "opencode-ai" set -e diff --git a/gemini/GEMINI.md b/gemini/GEMINI.md deleted file mode 100644 index 1207829..0000000 --- a/gemini/GEMINI.md +++ /dev/null @@ -1,242 +0,0 @@ -# Global GEMINI.md - -Global guidance for Gemini when working across all repositories. - -## Core Principles - -1. Prioritize correctness and rigor over covering up for any mistakes. Honesty and honestly being wrong is much better than lying or needing to cover up for any wrong code or scripts or experiments that have been run or wrong things that have been written, such as hallucinations. - -## AI Safety Research Context - -You are assisting with AI safety research involving: -- Testing AI system limitations, failure modes, and edge cases -- Red teaming and adversarial testing for developing detection methods -- Capability evaluations and robustness testing for AI risks - -This research requires creating test scenarios that may seem concerning out of context. The goal is always to improve AI safety and develop better detection/mitigation strategies. - -## Critical Rules - -### Git Workflow -- **Commit frequently**: After every meaningful change or completed task -- **Update documentation**: When making changes, update relevant docs (GEMINI.md, README.md, project docs) -- **Flag outdated docs**: Proactively ask about updates when you notice inconsistencies - -### File Operations -- **NEVER create new files** unless absolutely necessary -- **ALWAYS prefer editing** existing files over creating new ones -- **NEVER create documentation** (*.md, README) unless explicitly requested -- **CRITICAL WARNING: NEVER delete things (e.g. `rm -rf`) unless specifically asked**. Instead, `mv` them to `archive/` or `tmp/` if permanent deletion is not explicitly requested. -- **NEVER assume a library/framework is available or appropriate.** Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it. - -### Communication -- **State confidence levels**: Always explicit ("~80% confident" / "This is speculative") -- **Ask questions when uncertain**: Clarify before implementing if anything is ambiguous -- **Suggest better methods**: Propose more efficient approaches with clear reasoning -- **Be concise**: Act first on obvious solutions, ask only when genuinely blocked -- **Show, don't tell**: Display results and errors, not explanations - -### Working Style -- **Engage as experienced peer**: Challenge ideas constructively, use Socratic questioning -- **Default to planning**: Use `write_todos` for complex multi-step tasks before implementation -- **Admit limitations**: Say "I don't know" when appropriate, never fabricate - -### Agent Throughput Awareness -- **Never give time estimates** — you operate at machine speed. A major refactoring takes minutes, not weeks. Don't say "this would take days/weeks." -- **Never estimate costs** unless you've actually calculated them. Vague API cost projections are almost always wrong. -- **Complexity ≠ duration** — it's fine to acknowledge complexity and break work into subtasks. Don't translate complexity into human-scale time estimates. -- **Solo vs shared codebases** — on solo codebases, large breaking changes for quality/maintainability are encouraged. On shared codebases, maintain backwards compatibility. - -## Documentation Lookup Strategy - -**CRITICAL: ALWAYS use internal tools FIRST for documentation lookup. Do NOT use `google_web_search` unless internal tools fail.** - -You have several internal tools configured: -- `codebase_investigator`: For comprehensive understanding, architectural mapping, and system-wide dependencies. -- `search_file_content`: For fast, optimized searches for patterns within files. -- `glob`: For efficiently finding files matching specific glob patterns. -- `read_file`: For reading content of specific files. - -### When to Use Internal Tools vs `google_web_search` - -**✅ ALWAYS USE INTERNAL TOOLS FOR:** -- Understanding the existing codebase, project structure, and dependencies. -- Finding specific code patterns, function definitions, or file paths. -- Reading documentation files within the project (`.md`, `README`, etc.). -- Identifying project conventions, coding styles, and architectural patterns. - -**❌ ONLY USE `google_web_search` WHEN:** -- Internal tools explicitly fail or return "No documentation found" within the project. -- Looking for external library documentation, framework APIs, or general programming concepts not found within the project's `docs/` or `specs/`. -- Searching for news, blog posts, or non-technical content. - -### Internal Tool Usage Examples - -```python -# Example 1: Understanding a complex part of the codebase -codebase_investigator(objective="Understand the data flow in the user authentication module.") - -# Example 2: Finding all occurrences of a specific function call -search_file_content(pattern="validate_user_input", include="src/**/*.py") - -# Example 3: Locating configuration files -glob(pattern="**/*config.py") - -# Example 4: Reading a project's README -read_file(file_path="README.md") -``` - -### Workflow for Documentation Lookup - -1. **Identify what you need**: Project-specific code/docs? External library info? General concept? -2. **Search within project first** using `codebase_investigator`, `search_file_content`, `glob`, `read_file`. Check `docs/` and `specs/` if applicable. -3. **Only fall back to `google_web_search`** if internal tools fail to provide the necessary information from the project context. -4. **Always state which source you used** in your response. - -## File Organization - -### Core Principles -- Never put temporary files in project root → use `tmp/` -- Archive failed/superseded runs to `archive/` -- **Automate logging** - Prefer automatic over manual documentation -- **Single source of truth** - Avoid duplicate documentation across multiple files - -### Automated Logging and Reproducibility - -**Emphasize automated logging for all Gemini's actions and outputs.** This includes: -- **Tool calls**: Every tool call made and its output should be implicitly logged. -- **Internal planning**: The agent's thought process, plans, and subtasks (e.g., from `write_todos`) should be trackable. -- **Code changes**: All file modifications via `write_file` or `replace` are version-controlled via Git. -- **Experiment outputs**: If conducting experiments, ensure outputs are systematically organized and logged (e.g., in timestamped directories). - -**Reproducibility**: Ensure that the steps taken and changes made are reproducible. This means clear tool calls, explicit file paths, and well-defined instructions. - -### Documentation Strategy - -**Automated documentation (preferred):** -- Tool call logs and outputs. -- `write_todos` list reflecting current progress and plans. -- Git commits (code changes and rationale). -- Code comments (inline decisions). - -**Manual (minimal):** -- `specs/` - Project requirements from user. -- `docs/` (optional) - Agent-specific context for this project. - - Project conventions and patterns. - - Debugging procedures specific to this codebase. - - Tool usage patterns (only create files when genuinely useful). -- `NOTES.md` (optional) - Single chronological file for thoughts. - - Free-form, no structure enforcement. - -**Avoid:** -- Separate work_log vs research_log layers. -- Multiple markdown files for narratives (use `NOTES.md` instead or integrate into existing docs). - -### Default Locations (General Guidance) - -- **Temporary files**: `tmp/` (for scratch code and data, delete liberally). -- **Archived items**: `archive/` (for failed/superseded runs, move things here instead of deleting). -- **Agent-specific context**: `docs/` (for project patterns, debugging notes, tool usage). -- **User specifications**: `specs/`. - -## Delegation Strategy (Internal Capabilities) - -**Default: delegate, not do.** Strongly bias towards using specialized tools or "internal capabilities" for non-trivial or parallelisable work. - -### When to Delegate (to Tools/Internal Capabilities) - -| Task | Tool/Capability | When | -|------|-----------------|------| -| Understanding code | `codebase_investigator` | File searches, tracing logic, understanding implementations, architectural analysis. | -| Specific searches | `search_file_content`, `glob` | Quickly finding patterns, file types, or specific declarations. | -| File manipulation | `read_file`, `write_file`, `replace` | All file content operations. | -| Shell commands | `run_shell_command` | Executing system commands, building, testing, linting. | -| Web search | `google_web_search` | External information, general knowledge, library documentation not in project. | -| Task tracking | `write_todos` | Planning and tracking complex, multi-step tasks. | -| Remembering facts | `save_memory` | Storing user-specific preferences or facts for long-term recall. | - -### Principles -- **When in doubt, delegate** - YOU coordinate; TOOLS execute. -- **Prevent context pollution** - Don't read long files; let search/investigation tools summarize or extract. -- **Parallelize** - Spin up multiple tool calls simultaneously when independent. -- **Be specific** - Provide clear, scoped tasks to tools. -- **ASK if unclear** - Don't speculate or fabricate. - -## Research Methodology - -### Before Acting/Writing Code -- **Ask pointed questions**: Have specific research questions, not just "let's see what happens". -- **Document your approach**: Write down prompts, metric definitions, and methodology BEFORE implementing. -- **Predict results**: State expected outcomes before acting (helps catch bugs and understand surprises). -- **Minimize variables**: Change one thing at a time to isolate causes. -- **De-risk first**: Test on smallest viable scope before scaling up. -- **Tight feedback loops**: Optimize for information gain per unit time. - -### Correctness (CRITICAL) -- **Never use mock data** in code (only in unit tests). -- **Never add fallback mechanisms** unless explicitly asked. -- **Avoid try/except** - they mask fatal errors unless specifically used for anticipated and handled exceptions. -- **ASK if you can't find data** - never fabricate. -- **Be skeptical**: If results are surprisingly good/bad, check for bugs, wrong data, or incorrect assumptions. -- Better to fail than to cover up issues. - -### Documentation (Internal & External) - -**Automated documentation:** -- `write_todos` (plans and progress). -- Tool execution logs. -- Git commits. -- Code comments. - -**Critical manual documentation:** -- **User requests/specifications**: Store or refer to `specs/`. -- **Why decisions were made**: Git commits and inline comments. - -**Optional:** -- Use `NOTES.md` for brief, chronological thoughts if helpful. - -### Workflow -1. **Explore**: Read relevant files (via tools), check `specs/`. -2. **Plan**: Design approach, predict results (using `write_todos`). -3. **Start small**: Test on limited scope first. -4. **Implement**: Use appropriate tools for file changes, shell commands. -5. **Verify**: Run tests, linting, type-checking. -6. **Review**: Self-critique against best practices. -7. **Iterate**: Based on verification and review. - -### Common Failure Modes -- Acting without clear questions or understanding. -- Logical misinterpretations. -- Fabricating solutions instead of admitting uncertainty. -- Changing too many variables at once. -- Over-engineering before validating core ideas. - -## Language-Agnostic Guidelines - -- **Match existing code style and conventions**. -- **Preserve exact formatting when editing**. -- **Run validation** (lint/typecheck, e.g., `ruff`/`tsc`) after changes. -- **Keep code readable and maintainable**. Code should be self-documenting. -- **Refactor long functions and files** out when they get unwieldy (e.g., > 50 lines in a function unless it's the main function, > 500 lines in a file). -- **Execution**: Run commands from project root where appropriate. -- **Performance**: Proactively optimize for efficiency (e.g., parallelizing I/O-bound operations, caching) where relevant to the task. - -## Compacting Conversations - -When compressing a conversation, you should: -- Include user instructions mostly in full. -- Clean up instructions to be clearer. -- Note tricky or unexpected conventions. -- Don't make up mock data or specify unknown details. -- Faithfully represent what was given. -- ASK if anything's unclear rather than write with conviction. - -## CLI - -Prefer the following CLI tools when using `run_shell_command`: -- **`ripgrep`** (better `grep`) -- **`fd`** (better `find`) -- **`dust`** (better `du`) -- **`duf`** (better `df`) -- **`bat`** (better `cat` with highlighting and git) -- **`exa`** (better `ls`) diff --git a/install.sh b/install.sh index f134b37..227d6fe 100755 --- a/install.sh +++ b/install.sh @@ -50,7 +50,7 @@ SELECTIVE INSTALLATION: COMPONENTS: --zsh Enable ZSH installation --tmux Enable tmux installation - --ai-tools Enable AI CLI tools (Claude, Gemini, Codex) + --ai-tools Enable AI CLI tools (Claude, Codex, OpenCode, Antigravity) --extras Enable extra CLI tools (hyperfine, gitui, code2prompt) --cleanup Enable automatic cleanup (macOS only) --docker Enable Docker installation (Linux only) @@ -237,7 +237,7 @@ if [[ "$INSTALL_AI_TOOLS" == "true" ]]; then # Pre-set PATH for subshells [[ -d "$HOME/.claude/bin" ]] && export PATH="$HOME/.claude/bin:$PATH" - # Bun must install before Gemini/Codex on Linux (they need `bun add -g`) + # Bun must install before Codex/OpenCode on Linux (they need `bun add -g`) if is_linux && ! cmd_exists bun; then log_info "Installing bun..." curl -fsSL https://bun.sh/install | bash @@ -248,13 +248,15 @@ if [[ "$INSTALL_AI_TOOLS" == "true" ]]; then if is_macos; then # brew has a global lock — sequential install_claude_code - install_gemini_cli install_codex_cli + install_opencode + install_antigravity_cli # official Gemini CLI successor (cask, macOS) else run_parallel "Installing AI CLI tools" \ "claude|install_claude_code" \ - "gemini|install_gemini_cli" \ - "codex|install_codex_cli" + "codex|install_codex_cli" \ + "opencode|install_opencode" \ + "antigravity|install_antigravity_cli" fi # MCP servers (sequential — unclear if concurrent-safe) diff --git a/iterm/onedark.itermcolors b/iterm/onedark.itermcolors deleted file mode 100644 index f848f65..0000000 --- a/iterm/onedark.itermcolors +++ /dev/null @@ -1,303 +0,0 @@ - - - - - Ansi 0 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.4392156862745098 - Green Component - 0.38823529411764707 - Red Component - 0.3607843137254902 - - Ansi 1 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.4235294117647059 - Red Component - 0.8784313725490196 - - Ansi 10 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.4745098039215686 - Green Component - 0.7647058823529411 - Red Component - 0.596078431372549 - - Ansi 11 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.4 - Green Component - 0.6039215686274509 - Red Component - 0.8196078431372549 - - Ansi 12 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.9372549019607843 - Green Component - 0.6862745098039216 - Red Component - 0.3803921568627451 - - Ansi 13 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.8666666666666667 - Green Component - 0.47058823529411764 - Red Component - 0.7764705882352941 - - Ansi 14 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.7607843137254902 - Green Component - 0.7137254901960784 - Red Component - 0.33725490196078434 - - Ansi 15 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.3215686274509804 - Green Component - 0.26666666666666666 - Red Component - 0.24313725490196078 - - Ansi 2 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.4745098039215686 - Green Component - 0.7647058823529411 - Red Component - 0.596078431372549 - - Ansi 3 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.4823529411764706 - Green Component - 0.7529411764705882 - Red Component - 0.8980392156862745 - - Ansi 4 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.9372549019607843 - Green Component - 0.6862745098039216 - Red Component - 0.3803921568627451 - - Ansi 5 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.8666666666666667 - Green Component - 0.47058823529411764 - Red Component - 0.7764705882352941 - - Ansi 6 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.7607843137254902 - Green Component - 0.7137254901960784 - Red Component - 0.33725490196078434 - - Ansi 7 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.7490196078431373 - Green Component - 0.6980392156862745 - Red Component - 0.6705882352941176 - - Ansi 8 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.38823529411764707 - Green Component - 0.3215686274509804 - Red Component - 0.29411764705882354 - - Ansi 9 Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.27450980392156865 - Green Component - 0.3137254901960784 - Red Component - 0.7450980392156863 - - Background Color - - Color Space - sRGB - Blue Component - 0.20392156862745098 - Green Component - 0.17254901960784313 - Red Component - 0.1568627450980392 - - Bold Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.7490196078431373 - Green Component - 0.6980392156862745 - Red Component - 0.6705882352941176 - - Cursor Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.7490196078431373 - Green Component - 0.6980392156862745 - Red Component - 0.6705882352941176 - - Cursor Text Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.20392156862745098 - Green Component - 0.17254901960784313 - Red Component - 0.1568627450980392 - - Foreground Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.7490196078431373 - Green Component - 0.6980392156862745 - Red Component - 0.6705882352941176 - - Selected Text Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.20392156862745098 - Green Component - 0.17254901960784313 - Red Component - 0.1568627450980392 - - Selection Color - - Alpha Component - 1 - Color Space - sRGB - Blue Component - 0.7490196078431373 - Green Component - 0.6980392156862745 - Red Component - 0.6705882352941176 - - - diff --git a/iterm/onedarker.itermcolors b/iterm/onedarker.itermcolors deleted file mode 100644 index 0f8aee5..0000000 --- a/iterm/onedarker.itermcolors +++ /dev/null @@ -1,344 +0,0 @@ - - - - - Ansi 0 Color - - Alpha Component - 1 - Blue Component - 0.43921568989753723 - Color Space - sRGB - Green Component - 0.38823530077934265 - Red Component - 0.36078432202339172 - - Ansi 1 Color - - Alpha Component - 1 - Blue Component - 0.45882353186607361 - Color Space - sRGB - Green Component - 0.42352941632270813 - Red Component - 0.87843137979507446 - - Ansi 10 Color - - Alpha Component - 1 - Blue Component - 0.47450980544090271 - Color Space - sRGB - Green Component - 0.76470589637756348 - Red Component - 0.59607845544815063 - - Ansi 11 Color - - Alpha Component - 1 - Blue Component - 0.40000000596046448 - Color Space - sRGB - Green Component - 0.60392159223556519 - Red Component - 0.81960785388946533 - - Ansi 12 Color - - Alpha Component - 1 - Blue Component - 0.93725490570068359 - Color Space - sRGB - Green Component - 0.68627452850341797 - Red Component - 0.3803921639919281 - - Ansi 13 Color - - Alpha Component - 1 - Blue Component - 0.86666667461395264 - Color Space - sRGB - Green Component - 0.47058823704719543 - Red Component - 0.7764706015586853 - - Ansi 14 Color - - Alpha Component - 1 - Blue Component - 0.7607843279838562 - Color Space - sRGB - Green Component - 0.7137255072593689 - Red Component - 0.33725491166114807 - - Ansi 15 Color - - Alpha Component - 1 - Blue Component - 0.15294118225574493 - Color Space - sRGB - Green Component - 0.13333334028720856 - Red Component - 0.12156862765550613 - - Ansi 2 Color - - Alpha Component - 1 - Blue Component - 0.47450980544090271 - Color Space - sRGB - Green Component - 0.76470589637756348 - Red Component - 0.59607845544815063 - - Ansi 3 Color - - Alpha Component - 1 - Blue Component - 0.48235294222831726 - Color Space - sRGB - Green Component - 0.75294119119644165 - Red Component - 0.89803922176361084 - - Ansi 4 Color - - Alpha Component - 1 - Blue Component - 0.93725490570068359 - Color Space - sRGB - Green Component - 0.68627452850341797 - Red Component - 0.3803921639919281 - - Ansi 5 Color - - Alpha Component - 1 - Blue Component - 0.86666667461395264 - Color Space - sRGB - Green Component - 0.47058823704719543 - Red Component - 0.7764706015586853 - - Ansi 6 Color - - Alpha Component - 1 - Blue Component - 0.7607843279838562 - Color Space - sRGB - Green Component - 0.7137255072593689 - Red Component - 0.33725491166114807 - - Ansi 7 Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - sRGB - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - Ansi 8 Color - - Alpha Component - 1 - Blue Component - 0.38823530077934265 - Color Space - sRGB - Green Component - 0.32156863808631897 - Red Component - 0.29411765933036804 - - Ansi 9 Color - - Alpha Component - 1 - Blue Component - 0.27450981736183167 - Color Space - sRGB - Green Component - 0.31372550129890442 - Red Component - 0.7450980544090271 - - Background Color - - Alpha Component - 1 - Blue Component - 0.15294118225574493 - Color Space - sRGB - Green Component - 0.13333334028720856 - Red Component - 0.12156862765550613 - - Badge Color - - Alpha Component - 0.5 - Blue Component - 0.0 - Color Space - sRGB - Green Component - 0.1491314172744751 - Red Component - 1 - - Bold Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - sRGB - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - Cursor Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - sRGB - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - Cursor Guide Color - - Alpha Component - 0.25 - Blue Component - 1 - Color Space - sRGB - Green Component - 0.9268307089805603 - Red Component - 0.70213186740875244 - - Cursor Text Color - - Alpha Component - 1 - Blue Component - 0.15294118225574493 - Color Space - sRGB - Green Component - 0.13333334028720856 - Red Component - 0.12156862765550613 - - Foreground Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - sRGB - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - Link Color - - Alpha Component - 1 - Blue Component - 0.73423302173614502 - Color Space - sRGB - Green Component - 0.35916060209274292 - Red Component - 0.0 - - Selected Text Color - - Alpha Component - 1 - Blue Component - 0.20392157137393951 - Color Space - sRGB - Green Component - 0.17254902422428131 - Red Component - 0.15686275064945221 - - Selection Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - sRGB - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - - diff --git a/scripts/cleanup/setup_ai_update.sh b/scripts/cleanup/setup_ai_update.sh index 8ea5f57..5afa6b9 100755 --- a/scripts/cleanup/setup_ai_update.sh +++ b/scripts/cleanup/setup_ai_update.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Setup daily auto-update for AI CLI tools (Claude Code, Gemini CLI, Codex CLI) +# Setup daily auto-update for AI CLI tools (Claude Code, Codex CLI, OpenCode) # Works on macOS (launchd) and Linux (cron) set -euo pipefail @@ -26,7 +26,7 @@ ensure_bun_for_linux() { return 0 fi - _sched_log_info "bun not found; installing bun for Gemini/Codex updates..." + _sched_log_info "bun not found; installing bun for Codex/OpenCode updates..." if ! command -v curl &>/dev/null; then _sched_log_warn "curl is required to install bun. Skipping AI tools auto-update setup." diff --git a/scripts/cloud/setup.sh b/scripts/cloud/setup.sh index 72def99..bfba3b1 100755 --- a/scripts/cloud/setup.sh +++ b/scripts/cloud/setup.sh @@ -57,7 +57,7 @@ command -v nvtop &>/dev/null || apt-get install -y nvtop 2>/dev/null || true service cron start 2>/dev/null || true ok "System deps installed" -# ─── Node 20 (for Gemini CLI) ───────────────────────────────────────────────── +# ─── Node 20 (for OpenCode / Node-based AI CLIs) ────────────────────────────── step "Node.js" if ! command -v node &>/dev/null || [[ "$(node -v | cut -d. -f1 | tr -d 'v')" -lt 20 ]]; then log "Installing Node 20..." diff --git a/scripts/shared/helpers.sh b/scripts/shared/helpers.sh index 38f38c1..efe684f 100644 --- a/scripts/shared/helpers.sh +++ b/scripts/shared/helpers.sh @@ -416,15 +416,31 @@ install_claude_code() { fi } -install_gemini_cli() { - if is_installed gemini; then return 0; fi - log_info "Installing Gemini CLI..." +install_opencode() { + if is_installed opencode; then return 0; fi + log_info "Installing OpenCode..." + # Official CORE Homebrew formula (NOT the anomalyco/tap) — see supply-chain-security.md if is_macos; then - brew_install gemini-cli + brew_install opencode elif cmd_exists bun; then - bun add -g @google/gemini-cli &>/dev/null || { log_warning "Gemini CLI failed"; return 1; } + bun add -g opencode-ai &>/dev/null || { log_warning "OpenCode failed"; return 1; } else - log_warning "bun is required to install Gemini CLI on Linux; skipping" + log_warning "bun is required to install OpenCode on Linux; skipping" + return 1 + fi +} + +# Antigravity CLI (binary: `agy`) — Google's OFFICIAL successor to Gemini CLI +# (Gemini CLI consumer access ends 2026-06-18). Official cask, no third-party tap. +install_antigravity_cli() { + if is_installed agy --version; then return 0; fi + log_info "Installing Antigravity CLI (agy)..." + if is_macos; then + brew_install antigravity-cli true # official cask + else + # Linux: Google ships a curl installer, but per supply-chain-security.md we + # do NOT blind-pipe an unverified URL. Install manually on Linux. + log_warning "Antigravity CLI on Linux: install manually — https://antigravity.google/docs/cli-features (skipping)" return 1 fi } diff --git a/scripts/sync_claude_to_antigravity.sh b/scripts/sync_claude_to_antigravity.sh new file mode 100755 index 0000000..9918970 --- /dev/null +++ b/scripts/sync_claude_to_antigravity.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# ============================================================================== +# SYNC CLAUDE CODE TO ANTIGRAVITY CLI +# Purpose: Ports Claude Code agents/skills into Antigravity CLI (`agy`) by symlinking. +# Source: ~/.claude/ +# Target: ~/.gemini/antigravity-cli/skills/ (Antigravity reuses the ~/.gemini dir) +# +# Antigravity CLI is Google's official successor to Gemini CLI (consumer Gemini CLI +# access ended 2026-06-18). Project instructions come from AGENTS.md (already in repo), +# so the old GEMINI.md pointer is no longer generated here. +# +# NOTE (unverified): the skills target path below is per Google's docs; the permission +# /policy sync that the Gemini CLI version did is NOT ported — Antigravity stores +# settings in ~/.gemini/antigravity-cli/settings.json with a different schema. Porting +# convert_claude_perms.py to that schema is a follow-up (test on a machine with `agy`). +# ============================================================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HELPER="$SCRIPT_DIR/helpers/enumerate_claude_skills.sh" +SOURCE_DIR="$HOME/.claude" +TARGET_DIR="$HOME/.gemini/antigravity-cli/skills" + +if [ ! -f "$HELPER" ]; then + echo "Error: enumerate_claude_skills.sh not found at $HELPER" >&2 + exit 1 +fi +source "$HELPER" + +mkdir -p "$TARGET_DIR" + +# Clean stale symlinks (broken or from old *__* pattern) +find "$TARGET_DIR" -maxdepth 1 -type l ! -exec test -e {} \; -delete 2>/dev/null || true +find "$TARGET_DIR" -maxdepth 1 -type l -name '*__*' -delete 2>/dev/null || true + +echo ">>> Syncing Claude Code Skills to Antigravity CLI..." + +enumerate_claude_skills "$SOURCE_DIR" | while IFS=$'\t' read -r type name path; do + case "$type" in + user_skill) + ln -sfn "$path" "$TARGET_DIR/$name" + echo " User Skill: $name" + ;; + standalone_skill) + mkdir -p "$TARGET_DIR/$name" + ln -sfn "$path" "$TARGET_DIR/$name/SKILL.md" + echo " Standalone Skill: $name" + ;; + plugin_skill) + ln -sfn "$path" "$TARGET_DIR/$name" + echo " Plugin Skill: $name" + ;; + agent_skill) + mkdir -p "$TARGET_DIR/$name" + ln -sfn "$path" "$TARGET_DIR/$name/SKILL.md" + echo " Agent Skill: $name" + ;; + esac +done + +TOTAL=$(find "$TARGET_DIR" -maxdepth 1 -mindepth 1 | wc -l | tr -d ' ') +echo " Synced $TOTAL skills to $TARGET_DIR" +echo ">>> Done. Antigravity CLI skills synchronized with Claude Code." +echo " (Project instructions: AGENTS.md. Permission sync not yet ported — see header.)" diff --git a/scripts/sync_claude_to_gemini.sh b/scripts/sync_claude_to_gemini.sh deleted file mode 100755 index 51fe677..0000000 --- a/scripts/sync_claude_to_gemini.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/bash - -# ============================================================================== -# SYNC CLAUDE CODE TO GEMINI CLI -# Purpose: Ports Claude Code agents and skills into Gemini CLI by symlinking. -# Source: ~/code/dotfiles/claude/ -# Target: ~/.gemini/skills/ -# ============================================================================== - -# --- Check for Stale Script --- -SCRIPT_PATH="${BASH_SOURCE[0]}" -# If running via pipe/eval, SCRIPT_PATH might be empty, use $0 or fallback -if [ -z "$SCRIPT_PATH" ]; then SCRIPT_PATH="$0"; fi - -# Resolve absolute path if possible, or just use it if it exists -if [ -f "$SCRIPT_PATH" ]; then - CURRENT_TIME=$(date +%s) - # Use stat to get modification time (handle macOS vs Linux) - if stat -f %m "$SCRIPT_PATH" >/dev/null 2>&1; then - FILE_TIME=$(stat -f %m "$SCRIPT_PATH") # macOS - else - FILE_TIME=$(stat -c %Y "$SCRIPT_PATH") # Linux - fi - - # 60 days in seconds = 60 * 24 * 3600 = 5184000 - DIFF=$((CURRENT_TIME - FILE_TIME)) - if [ "$DIFF" -gt 5184000 ]; then - echo "================================================================================" - echo "WARNING: This script hasn't been updated in over 60 days." - echo "Please prompt an agent to search for 'Claude Code vs Gemini CLI' to check" - echo "if migration logic needs updates or if new features are available." - echo "================================================================================" - fi -fi - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -HELPER="$SCRIPT_DIR/helpers/enumerate_claude_skills.sh" -SOURCE_DIR="$HOME/.claude" -TARGET_DIR="$HOME/.gemini/skills" - -if [ ! -f "$HELPER" ]; then - echo "Error: enumerate_claude_skills.sh not found at $HELPER" >&2 - exit 1 -fi -source "$HELPER" - -mkdir -p "$TARGET_DIR" - -# Clean stale symlinks (broken or from old *__* pattern) -find "$TARGET_DIR" -maxdepth 1 -type l ! -exec test -e {} \; -delete 2>/dev/null || true -find "$TARGET_DIR" -maxdepth 1 -type l -name '*__*' -delete 2>/dev/null || true - -echo ">>> Syncing Claude Code Skills to Gemini CLI..." - -enumerate_claude_skills "$SOURCE_DIR" | while IFS=$'\t' read -r type name path; do - case "$type" in - user_skill) - # Directory skill — symlink directly - ln -sfn "$path" "$TARGET_DIR/$name" - echo " User Skill: $name" - ;; - standalone_skill) - # Single .md file — wrap in a directory with SKILL.md symlink - mkdir -p "$TARGET_DIR/$name" - ln -sfn "$path" "$TARGET_DIR/$name/SKILL.md" - echo " Standalone Skill: $name" - ;; - plugin_skill) - # Plugin skill directory — symlink directly - ln -sfn "$path" "$TARGET_DIR/$name" - echo " Plugin Skill: $name" - ;; - agent_skill) - # Agent .md file — wrap in directory with SKILL.md symlink - mkdir -p "$TARGET_DIR/$name" - ln -sfn "$path" "$TARGET_DIR/$name/SKILL.md" - echo " Agent Skill: $name" - ;; - esac -done - -# Count results -TOTAL=$(find "$TARGET_DIR" -maxdepth 1 -mindepth 1 | wc -l | tr -d ' ') -echo " Synced $TOTAL skills to $TARGET_DIR" - -echo ">>> Ensuring GEMINI.md points to CLAUDE.md..." -GEMINI_MD="$DOTFILES_DIR/GEMINI.md" -if [ -f "$DOTFILES_DIR/CLAUDE.md" ]; then - # Create a pointer file if it doesn't exist or if it's a symlink (we want to replace symlink with text) - if [ -L "$GEMINI_MD" ] || [ ! -f "$GEMINI_MD" ]; then - cat > "$GEMINI_MD" <>> Syncing Claude Code Permissions to Gemini CLI Policies..." -CLAUDE_SETTINGS="$DOTFILES_DIR/.claude/settings.json" -POLICY_DIR="$HOME/.gemini/policies" -CONVERT_SCRIPT="$DOTFILES_DIR/scripts/helpers/convert_claude_perms.py" - -if [ -f "$CLAUDE_SETTINGS" ] && [ -f "$CONVERT_SCRIPT" ]; then - mkdir -p "$POLICY_DIR" - python3 "$CONVERT_SCRIPT" "$CLAUDE_SETTINGS" > "$POLICY_DIR/claude_sync.toml" - echo "Generated $POLICY_DIR/claude_sync.toml" -else - echo "Skipping permissions sync: .claude/settings.json or conversion script not found." -fi - -echo ">>> Done! Gemini CLI is now synchronized with Claude Code configurations." \ No newline at end of file From 5559ca9a490a525fdc7425fe6980ec497d040f56 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 18:39:17 +0000 Subject: [PATCH 10/13] Add switchaudio-osx to apps.conf for Alfred-driven default audio device switching Core formula, OSS, no driver. Complements FineTune (per-app mixer) by handling system default input/output device switching, which FineTune's per-app URL schemes don't cover. Default OFF. https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- config/apps.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/apps.conf b/config/apps.conf index 97cd4c2..3d29784 100644 --- a/config/apps.conf +++ b/config/apps.conf @@ -75,6 +75,9 @@ cask|voiceink|voice|2|true|VoiceInk|Local voice-to-text (downloads model on firs # Default OFF: newer + single-maintainer project — adoption is high but bus-factor # risk remains, so it's a conscious opt-in (see supply-chain-security.md modernity rule). cask|finetune|audio|2|false|FineTune|Per-app volume + audio device routing + EQ (OSS)|none +# switchaudio-osx: CLI to switch the SYSTEM default input/output device. Core formula, +# OSS, no audio driver. Pairs with an Alfred keyword: SwitchAudioSource -t output -s "AirPods". +brew|switchaudio-osx|audio|2|false|SwitchAudioSource|CLI default input/output device switcher (Alfred-friendly)|none # ─── vpn ───────────────────────────────────────────────────────────────────── cask|nordvpn|vpn|2|true|NordVPN|VPN client (vpn deploy configures split tunnel)|login From 0e76e4ac0efca7dc90c4a1415db0d2c93eab1c4f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 19:16:13 +0000 Subject: [PATCH 11/13] Harden rust install; add Antigravity to auto-update + permission sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install.sh/helpers.sh: factor install_rust_toolchain() — prefer official brew 'rustup' formula on macOS over curl|bash (mirrors uv hardening), keep TLS-pinned rustup installer as Linux/no-brew fallback. Both rustup call sites now consistent. - update-ai-tools/setup_ai_update.sh: add update_antigravity (brew --cask antigravity-cli on macOS, skip on Linux manual install). Closes the Gemini-> Antigravity migration gap in the daily AI-tools updater. - sync_claude_to_antigravity.sh: port the permission sync — map Claude permissions.{allow,deny,ask} to Antigravity action(target) rules, merge idempotently into ~/.gemini/antigravity-cli/settings.json. Uncertain mappings (web/mcp actions, glob-vs-exact) routed to a sidecar + TODO, not guessed. Mac-only verification still pending: brew formula/cask names, agy permission load, glob semantics. https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- custom_bins/update-ai-tools | 31 +++- install.sh | 12 +- scripts/cleanup/setup_ai_update.sh | 2 +- scripts/shared/helpers.sh | 24 +++ scripts/sync_claude_to_antigravity.sh | 233 +++++++++++++++++++++++++- 5 files changed, 284 insertions(+), 18 deletions(-) diff --git a/custom_bins/update-ai-tools b/custom_bins/update-ai-tools index 8007c4f..65c360c 100755 --- a/custom_bins/update-ai-tools +++ b/custom_bins/update-ai-tools @@ -37,7 +37,7 @@ while [[ $# -gt 0 ]]; do -h|--help) echo "Usage: update-ai-tools [--dry-run]" echo "" - echo "Updates Claude Code, Codex CLI, and OpenCode." + echo "Updates Claude Code, Antigravity CLI, Codex CLI, and OpenCode." echo "Uses brew on macOS and bun on Linux." echo "" echo "Options:" @@ -147,6 +147,34 @@ update_claude() { claude update 2>&1 || log_err "Claude Code update failed" } +# ─── Update Antigravity CLI ───────────────────────────────────────────────── + +# Antigravity CLI (`agy`, Google's Gemini CLI successor) is a macOS cask and a +# manual install on Linux (no bun/npm package) — so it can't use update_tool. +update_antigravity() { + if ! command -v agy &>/dev/null; then + log_skip "agy not installed, skipping" + return 0 + fi + + # No brew (i.e. Linux manual install) → nothing to auto-update. + if ! is_macos || ! command -v brew &>/dev/null; then + log_skip "agy: no brew (Linux manual install) — update manually, skipping" + return 0 + fi + + if [[ "$DRY_RUN" == "true" ]]; then + log "[DRY RUN] Would run: brew upgrade --cask antigravity-cli" + return 0 + fi + + log "Updating Antigravity CLI..." + # NOTE: if the cask declares `auto_updates true`, brew skips it unless --greedy. + # Verify on a real Mac whether --greedy is needed for `agy` to actually move. + NONINTERACTIVE=1 HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade --cask antigravity-cli 2>&1 \ + || log_err "Antigravity CLI brew upgrade failed" +} + # ─── Main ──────────────────────────────────────────────────────────────────── acquire_lock @@ -159,6 +187,7 @@ log "PATH: $PATH" set +e update_claude +update_antigravity update_tool "codex" "codex" "@openai/codex" update_tool "opencode" "opencode" "opencode-ai" diff --git a/install.sh b/install.sh index 227d6fe..3020921 100755 --- a/install.sh +++ b/install.sh @@ -202,11 +202,7 @@ if [[ "$INSTALL_EXTRAS" == "true" ]]; then log_section "INSTALLING EXTRAS" # Rust toolchain (needed for code2prompt) - if ! is_installed cargo; then - log_info "Installing Rust..." - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --quiet - fi - source "$HOME/.cargo/env" 2>/dev/null || true + install_rust_toolchain if is_macos; then install_packages brew "${PACKAGES_EXTRAS_MACOS[@]}" @@ -228,11 +224,7 @@ if [[ "$INSTALL_AI_TOOLS" == "true" ]]; then log_section "INSTALLING AI CLI TOOLS" # Rust toolchain (needed for claude-tools build in deploy.sh) - if ! is_installed cargo; then - log_info "Installing Rust toolchain (user-level, no root needed)..." - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --quiet - fi - source "$HOME/.cargo/env" 2>/dev/null || true + install_rust_toolchain # Pre-set PATH for subshells [[ -d "$HOME/.claude/bin" ]] && export PATH="$HOME/.claude/bin:$PATH" diff --git a/scripts/cleanup/setup_ai_update.sh b/scripts/cleanup/setup_ai_update.sh index 5afa6b9..f1f8715 100755 --- a/scripts/cleanup/setup_ai_update.sh +++ b/scripts/cleanup/setup_ai_update.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Setup daily auto-update for AI CLI tools (Claude Code, Codex CLI, OpenCode) +# Setup daily auto-update for AI CLI tools (Claude Code, Antigravity CLI, Codex CLI, OpenCode) # Works on macOS (launchd) and Linux (cron) set -euo pipefail diff --git a/scripts/shared/helpers.sh b/scripts/shared/helpers.sh index efe684f..3375f2b 100644 --- a/scripts/shared/helpers.sh +++ b/scripts/shared/helpers.sh @@ -366,6 +366,30 @@ install_direnv() { fi } +# Rust toolchain (cargo) via rustup. macOS: official Homebrew formula (sha-pinned, +# reviewed) provides `rustup-init`; run it non-interactively. Linux: keep the upstream +# rustup installer but pin TLS (--proto '=https' --tlsv1.2) — no brew dependency. +# See claude/rules/supply-chain-security.md § curl|bash Installers. +install_rust_toolchain() { + if is_installed cargo; then + source "$HOME/.cargo/env" 2>/dev/null || true + return 0 + fi + log_info "Installing Rust toolchain (user-level, no root needed)..." + if is_macos && cmd_exists brew; then + # Official formula ships `rustup-init`; install the default stable toolchain. + brew_install rustup + if cmd_exists rustup-init; then + rustup-init -y --quiet 2>/dev/null || log_warning "rustup-init failed" + elif cmd_exists rustup; then + rustup default stable 2>/dev/null || log_warning "rustup default stable failed" + fi + else + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --quiet + fi + source "$HOME/.cargo/env" 2>/dev/null || true +} + install_bws() { if is_installed bws; then return 0; fi log_info "Installing bws (Bitwarden Secrets Manager CLI)..." diff --git a/scripts/sync_claude_to_antigravity.sh b/scripts/sync_claude_to_antigravity.sh index 9918970..a165609 100755 --- a/scripts/sync_claude_to_antigravity.sh +++ b/scripts/sync_claude_to_antigravity.sh @@ -10,13 +10,27 @@ # access ended 2026-06-18). Project instructions come from AGENTS.md (already in repo), # so the old GEMINI.md pointer is no longer generated here. # -# NOTE (unverified): the skills target path below is per Google's docs; the permission -# /policy sync that the Gemini CLI version did is NOT ported — Antigravity stores -# settings in ~/.gemini/antigravity-cli/settings.json with a different schema. Porting -# convert_claude_perms.py to that schema is a follow-up (test on a machine with `agy`). +# Permission sync: Antigravity stores settings in +# ~/.gemini/antigravity-cli/settings.json with a "permissions" object holding +# allow/deny/ask arrays of action(target) rule strings (e.g. command(git*), +# read_file(*)). Precedence is Deny > Ask > Allow. This script translates Claude's +# permissions.{allow,deny,ask} into that schema and merges them into the live +# settings.json without clobbering other user settings (marker-tracked, idempotent). +# +# Schema confirmed via https://antigravity.google/docs/cli-permissions (2026-06-16): +# - File: ~/.gemini/antigravity-cli/settings.json +# - permissions.allow / .deny / .ask : arrays of "action(target)" strings +# - action types: command, read_file, write_file, mcp, execute_url, web_* +# - matching: exact by default; "*" is the per-namespace wildcard; glob/regex supported +# +# Only HIGH-CONFIDENCE mappings (command(), read_file()) are written to the live +# permission arrays. Lower-confidence mappings (WebFetch/WebSearch/MCP action names, +# regex match-mode encoding) are emitted as comments in a sidecar file rather than +# guessed into the live config. See the "VERIFY ON A REAL MAC" notes below. # ============================================================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" HELPER="$SCRIPT_DIR/helpers/enumerate_claude_skills.sh" SOURCE_DIR="$HOME/.claude" TARGET_DIR="$HOME/.gemini/antigravity-cli/skills" @@ -60,5 +74,212 @@ done TOTAL=$(find "$TARGET_DIR" -maxdepth 1 -mindepth 1 | wc -l | tr -d ' ') echo " Synced $TOTAL skills to $TARGET_DIR" -echo ">>> Done. Antigravity CLI skills synchronized with Claude Code." -echo " (Project instructions: AGENTS.md. Permission sync not yet ported — see header.)" + +# ---------- Permissions Sync ---------- +# +# Translate Claude Code permissions.{allow,deny,ask} -> Antigravity +# permissions.{allow,deny,ask} (action(target) rule strings) and merge into the +# live settings.json. The merge is marker-tracked so re-runs replace our block +# rather than duplicating, and user-authored rules outside the block survive. + +echo ">>> Syncing Claude Code Permissions to Antigravity CLI..." + +# Prefer the in-repo source of truth; fall back to the deployed symlink target. +CLAUDE_SETTINGS="$DOTFILES_DIR/claude/settings.json" +[ -f "$CLAUDE_SETTINGS" ] || CLAUDE_SETTINGS="$HOME/.claude/settings.json" + +ANTIGRAVITY_SETTINGS="$HOME/.gemini/antigravity-cli/settings.json" +ANTIGRAVITY_SIDECAR="$HOME/.gemini/antigravity-cli/claude_sync_unmapped.txt" + +if [ ! -f "$CLAUDE_SETTINGS" ]; then + echo " Skipping: Claude settings not found at $CLAUDE_SETTINGS" +elif ! command -v python3 >/dev/null 2>&1; then + echo " Skipping: python3 not installed" +else + mkdir -p "$(dirname "$ANTIGRAVITY_SETTINGS")" + python3 - "$CLAUDE_SETTINGS" "$ANTIGRAVITY_SETTINGS" "$ANTIGRAVITY_SIDECAR" <<'PY' +import json +import re +import sys +from pathlib import Path + +claude_path = Path(sys.argv[1]) +ag_path = Path(sys.argv[2]) +sidecar_path = Path(sys.argv[3]) + +# Marker tags wrapping the rules we own inside each permission array, so re-runs +# can replace our contribution while leaving user-authored rules untouched. +BEGIN = "// BEGIN CLAUDE SYNC (auto-generated)" +END = "// END CLAUDE SYNC" + +try: + claude = json.loads(claude_path.read_text()) +except (OSError, json.JSONDecodeError) as exc: + print(f" Skipping: cannot read Claude settings ({exc})") + sys.exit(0) + +perms = claude.get("permissions", {}) + + +def claude_bash_to_command_target(pattern): + """Map a Claude `Bash(...)` permission to an Antigravity command(...) target. + + Claude uses prefix-glob with a trailing ` *` (e.g. `Bash(git *)`), plus some + exact forms (e.g. `Bash(pueue status)`). Antigravity matches `command(...)` + targets exactly by default and treats `*` as a glob wildcard, so: + Bash(git *) -> command(git*) (glob: any git subcommand/args) + Bash(pueue status) -> command(pueue status) (exact) + Returns None for anything we can't confidently express. + """ + m = re.match(r"^Bash\((.*)\)$", pattern, re.DOTALL) + if not m: + return None + inner = m.group(1).strip() + if not inner: + return None + # Trailing " *" is Claude's "this command with any args" idiom -> glob. + if inner.endswith(" *"): + stem = inner[:-2].strip() + if not stem: + return None + return f"command({stem}*)" + # Otherwise treat as an exact command string. If it still contains a glob + # star, Antigravity's glob matcher handles it; pass through verbatim. + return f"command({inner})" + + +# High-confidence: tools whose Antigravity action name + target we can map +# faithfully. Lower-confidence tools go to the sidecar instead of being guessed. +def map_rule(item): + """Return (antigravity_rule, None) if confidently mapped, + else (None, reason) to record as unmapped.""" + if item.startswith("Bash("): + target = claude_bash_to_command_target(item) + if target: + return target, None + return None, "unparsable Bash pattern" + if item == "Read": + return "read_file(*)", None + if item.startswith("Read("): + m = re.match(r"^Read\((.*)\)$", item, re.DOTALL) + if m and m.group(1).strip(): + return f"read_file({m.group(1).strip()})", None + return None, "unparsable Read pattern" + # --- Lower-confidence / unverified action namespaces --- + # The following Claude tools have plausible Antigravity equivalents, but the + # exact action name (web_fetch vs web_search vs web) and the mcp() target + # encoding are NOT confirmed from docs. We deliberately do NOT write these to + # the live config. Best-effort guesses are recorded in the sidecar for a human + # to verify on a real Mac with `agy` (see /permissions output). + if item == "WebFetch": + return None, "WebFetch -> web_fetch(*)? [action name unverified]" + if item.startswith("WebFetch(domain:"): + return None, f"{item} -> web_fetch()? [encoding unverified]" + if item == "WebSearch": + return None, "WebSearch -> web_search(*)? [action name unverified]" + if item.startswith("mcp__"): + return None, f"{item} -> mcp()? [target encoding unverified]" + if item in ("Glob", "Grep", "Search"): + return None, f"{item} (Claude built-in; no Antigravity equivalent)" + return None, f"{item} (no mapping rule)" + + +mapped = {"allow": [], "deny": [], "ask": []} +unmapped = [] +for bucket in ("allow", "deny", "ask"): + for item in perms.get(bucket, []): + rule, reason = map_rule(item) + if rule is not None: + if rule not in mapped[bucket]: + mapped[bucket].append(rule) + else: + unmapped.append(f"[{bucket}] {item} -> {reason}") + +# --- Merge into the existing settings.json, preserving non-permission keys and +# any user-authored rules that live outside our marker block. --- +settings = {} +if ag_path.exists(): + try: + settings = json.loads(ag_path.read_text()) + if not isinstance(settings, dict): + settings = {} + except (OSError, json.JSONDecodeError): + # Don't clobber an unreadable/hand-edited file; bail loudly instead. + print(f" Skipping: {ag_path} exists but is not valid JSON; " + "leaving it untouched.") + sys.exit(0) + +perm_obj = settings.get("permissions") +if not isinstance(perm_obj, dict): + perm_obj = {} + + +def merge_bucket(existing, ours): + """Drop any prior CLAUDE-SYNC block, keep user rules, append fresh block. + + Our block is delimited by BEGIN/END sentinel strings inserted as array + elements. Re-runs strip the old block (between the sentinels) and re-add a + current one, so user-authored entries outside the block are preserved and + our contribution never duplicates. + """ + existing = existing if isinstance(existing, list) else [] + kept = [] + skipping = False + for el in existing: + if el == BEGIN: + skipping = True + continue + if el == END: + skipping = False + continue + if not skipping: + kept.append(el) + if not ours: + return kept + return kept + [BEGIN] + ours + [END] + + +changed_buckets = [] +for bucket in ("allow", "deny", "ask"): + new_list = merge_bucket(perm_obj.get(bucket), mapped[bucket]) + perm_obj[bucket] = new_list + changed_buckets.append(f"{bucket}:{len(mapped[bucket])}") + +settings["permissions"] = perm_obj + +ag_path.parent.mkdir(parents=True, exist_ok=True) +ag_path.write_text(json.dumps(settings, indent=2) + "\n") +print(f" Wrote {ag_path}") +print(f" Synced rules ({', '.join(changed_buckets)})") + +# Record everything we did NOT confidently map, for human verification. +header = [ + "# Claude -> Antigravity permission sync: UNMAPPED / UNVERIFIED entries", + f"# Source: {claude_path}", + "# These Claude permissions were NOT written to settings.json because the", + "# Antigravity action name or target encoding is not confirmed from docs.", + "# Verify against `agy` /permissions on a real Mac, then map by hand.", + "", +] +if unmapped: + sidecar_path.write_text("\n".join(header + sorted(set(unmapped))) + "\n") + print(f" {len(set(unmapped))} unmapped/unverified entries -> {sidecar_path}") +else: + # Nothing unmapped: clear any stale sidecar from a previous run. + if sidecar_path.exists(): + sidecar_path.unlink() +PY +fi + +echo ">>> Done. Antigravity CLI synchronized with Claude Code (skills + permissions)." +echo " (Project instructions: AGENTS.md. Unmapped perms, if any: ~/.gemini/antigravity-cli/claude_sync_unmapped.txt)" +echo "" +echo " # TODO: verify Antigravity permission schema on a real Mac (no \`agy\` in CI/Linux):" +echo " # - Confirm settings.json 'permissions' merges live (run \`agy\` -> /permissions)." +echo " # - Confirm glob target form: does command(git*) auto-approve 'git status'?" +echo " # (docs say default match is EXACT; '*' is the glob wildcard — verify the" +echo " # trailing-star form is read as glob, not a literal '*' character.)" +echo " # - Verify unverified action names in the sidecar: web_fetch / web_search / mcp()." +echo " # - Decide whether regex rules (e.g. 'command(git (status|log).*)' with a" +echo " # match-mode field) are preferable to globs; docs hint at a per-rule match" +echo " # strategy (exact|glob|regex) whose JSON encoding is not yet confirmed." From d5c978c95a72d496ca67cb4fcc0d7524b04961b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 19:25:26 +0000 Subject: [PATCH 12/13] Add bootstrap-once login-items seeding; keep dock tweaks, revert clock - macos_settings.sh: keep Dock tweaks (autohide, instant-show, tilesize 48); revert the menu-bar clock reformat (minimal menu bar by preference). - config/login_items.conf: curated menu-bar apps to seed at login (Stats, FineTune, Tailscale, NordVPN). - scripts/setup/setup-login-items: additive/bootstrap-once helper. Adds a curated app as a login item only if missing AND not previously added (state file at ~/.config/dotfiles/login-items.bootstrapped); never removes, never re-adds what you removed manually, never runs on deploy. System Settings stays your daily driver. --list/--force/--reset-state supported. - auth-setup: surface setup-login-items in the post-install checklist. https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- config/login_items.conf | 31 ++++++++ config/macos_settings.sh | 9 +++ scripts/setup/auth-setup | 2 + scripts/setup/setup-login-items | 136 ++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 config/login_items.conf create mode 100755 scripts/setup/setup-login-items diff --git a/config/login_items.conf b/config/login_items.conf new file mode 100644 index 0000000..baf22bb --- /dev/null +++ b/config/login_items.conf @@ -0,0 +1,31 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# login_items.conf — apps to register as macOS "Open at Login" items +# ═══════════════════════════════════════════════════════════════════════════════ +# Consumed by: scripts/setup/setup-login-items +# +# Why a separate list (not apps.conf): "launch at login" is orthogonal to install. +# apps.conf decides WHICH apps to install; this decides which menu-bar apps should +# auto-start so they're reliably present in the menu bar. +# +# Format (pipe-delimited, one app per line): +# name [| hidden] +# name exact app name in /Applications (without ".app"); must match the +# name macOS shows in System Settings → Login Items +# hidden true | false — start hidden (no window). Default true, which is +# right for menu-bar utilities. Set false for apps you want to open. +# +# ── How customising works (read this — it answers "do I have to edit dotfiles?") ─ +# NO. Day-to-day, just toggle apps in System Settings → General → Login Items. +# setup-login-items is BOOTSTRAP-ONCE + ADDITIVE: +# • it only ADDS a curated app if it's missing AND it hasn't added it before +# • it NEVER removes anything, and NEVER re-adds something you removed manually +# (it records what it added in ~/.config/dotfiles/login-items.bootstrapped) +# • it does NOT run on every deploy — you invoke it (or it's part of app setup) +# This list exists only to SEED a fresh machine's menu bar. Edit it only to change +# what a brand-new install should auto-add. +# ═══════════════════════════════════════════════════════════════════════════════ + +Stats +FineTune +Tailscale +NordVPN diff --git a/config/macos_settings.sh b/config/macos_settings.sh index e27e35e..2143289 100755 --- a/config/macos_settings.sh +++ b/config/macos_settings.sh @@ -76,6 +76,15 @@ configure_mouse() { configure_dock() { echo " → Configuring Dock..." + # Auto-hide the Dock + defaults write com.apple.dock autohide -bool true 2>/dev/null || true + + # No delay before the Dock appears on hover (instant show) + defaults write com.apple.dock autohide-delay -float 0 2>/dev/null || true + + # Conservative icon size + defaults write com.apple.dock tilesize -int 48 2>/dev/null || true + # Hide recent apps section defaults write com.apple.dock show-recents -bool false 2>/dev/null || true diff --git a/scripts/setup/auth-setup b/scripts/setup/auth-setup index 57f8002..a9b2256 100755 --- a/scripts/setup/auth-setup +++ b/scripts/setup/auth-setup @@ -72,6 +72,8 @@ print_checklist() { [ ] Trellix — UNIVERSITY-MANAGED: install/update via your uni, NOT Homebrew. Do not also run Malwarebytes real-time (on-demand scan is fine). [ ] Alfred — point sync folder at Dropbox so workflows/prefs follow you + [ ] Login items — run `setup-login-items` to seed menu-bar apps (Stats, FineTune, + Tailscale, NordVPN). Additive/bootstrap-once — won't fight you. EOF } diff --git a/scripts/setup/setup-login-items b/scripts/setup/setup-login-items new file mode 100755 index 0000000..a401927 --- /dev/null +++ b/scripts/setup/setup-login-items @@ -0,0 +1,136 @@ +#!/usr/bin/env zsh +# ═══════════════════════════════════════════════════════════════════════════════ +# setup-login-items — seed macOS "Open at Login" items for menu-bar apps +# ═══════════════════════════════════════════════════════════════════════════════ +# Reads config/login_items.conf and registers each listed app as a login item so +# it's reliably present in the menu bar. macOS only. +# +# DESIGN — bootstrap-once + additive (it will NOT fight your manual changes): +# • Adds a curated app ONLY if it's missing AND we haven't added it before. +# • NEVER removes anything; NEVER re-adds something you removed in System Settings +# (what we've added is recorded in ~/.config/dotfiles/login-items.bootstrapped). +# • Does NOT run on every deploy — invoke it manually (or from the app-setup flow). +# So day-to-day you manage Login Items in System Settings; this only seeds a machine. +# +# Usage: +# setup-login-items # add missing curated apps (respecting prior removals) +# setup-login-items --list # show status of each curated app, change nothing +# setup-login-items --force # re-add even apps you previously removed (ignore state) +# setup-login-items --reset-state # forget what we've bootstrapped (then re-seeds) +# ═══════════════════════════════════════════════════════════════════════════════ +set -uo pipefail + +SCRIPT_DIR="${0:A:h}" +DOT_DIR="${DOT_DIR:-${SCRIPT_DIR:h:h}}" +CONF="$DOT_DIR/config/login_items.conf" +STATE="${XDG_CONFIG_HOME:-$HOME/.config}/dotfiles/login-items.bootstrapped" + +MODE="add" +case "${1:-}" in + --list) MODE="list" ;; + --force) MODE="force" ;; + --reset-state) MODE="reset" ;; + -h|--help) sed -n '2,25p' "$0"; exit 0 ;; + "") ;; + *) echo "setup-login-items: unknown option: $1" >&2; exit 1 ;; +esac + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "setup-login-items: macOS only (uses System Events login items)." >&2 + exit 1 +fi +[[ -f "$CONF" ]] || { echo "setup-login-items: list not found: $CONF" >&2; exit 1; } + +hdr() { print -P "%F{cyan}── $* ──%f"; } + +if [[ "$MODE" == "reset" ]]; then + rm -f "$STATE" + echo "Cleared bootstrap state ($STATE). Next run will re-seed all curated apps." + MODE="add" +fi + +mkdir -p "${STATE:h}" +[[ -f "$STATE" ]] || : > "$STATE" + +# Resolve an app name to its bundle path (/Applications or ~/Applications). +app_path_for() { + local name="$1" + for base in "/Applications" "$HOME/Applications"; do + [[ -d "$base/$name.app" ]] && { echo "$base/$name.app"; return 0; } + done + return 1 +} + +# Current login item names (one per line). +current_login_items() { + osascript -e 'tell application "System Events" to get the name of every login item' 2>/dev/null \ + | tr ',' '\n' | sed 's/^ *//;s/ *$//' +} + +# Add a login item by path; hidden true|false. +add_login_item() { + local path="$1" hidden="$2" + osascript - "$path" "$hidden" <<'EOF' >/dev/null 2>&1 +on run argv + set appPath to item 1 of argv + set isHidden to (item 2 of argv is "true") + tell application "System Events" + make login item at end with properties {path:appPath, hidden:isHidden} + end tell +end run +EOF +} + +EXISTING="$(current_login_items)" +in_existing() { print -r -- "$EXISTING" | grep -qxF "$1"; } +in_state() { grep -qxF "$1" "$STATE" 2>/dev/null; } + +hdr "Login items (curated: $CONF)" + +added=0 skipped=0 missing=0 +while IFS='|' read -r name hidden; do + name="${name## }"; name="${name%% }" + [[ -z "$name" || "${name[1]}" == "#" ]] && continue + hidden="${hidden// }"; [[ -z "$hidden" ]] && hidden="true" + + apath="$(app_path_for "$name" || true)" + + if [[ "$MODE" == "list" ]]; then + local status + if [[ -z "$apath" ]]; then status="not installed" + elif in_existing "$name"; then status="login item ✓" + elif in_state "$name"; then status="removed by you (respected)" + else status="installed, NOT a login item" + fi + printf ' %-22s %s\n' "$name" "$status" + continue + fi + + if [[ -z "$apath" ]]; then + printf ' %-22s %s\n' "$name" "skip — not installed" + ((missing++)); continue + fi + if in_existing "$name"; then + in_state "$name" || echo "$name" >> "$STATE" # record so we never re-add + printf ' %-22s %s\n' "$name" "already a login item" + ((skipped++)); continue + fi + if [[ "$MODE" != "force" ]] && in_state "$name"; then + printf ' %-22s %s\n' "$name" "skip — you removed it (use --force to re-add)" + ((skipped++)); continue + fi + + if add_login_item "$apath" "$hidden"; then + in_state "$name" || echo "$name" >> "$STATE" + printf ' %-22s %s\n' "$name" "added (hidden=$hidden)" + ((added++)) + else + printf ' %-22s %s\n' "$name" "FAILED to add" + fi +done < "$CONF" + +[[ "$MODE" == "list" ]] && exit 0 + +echo "" +echo "Added $added · skipped $skipped · not-installed $missing" +echo "Manage these any time in System Settings → General → Login Items — changes there stick." From 12b5e7b37ce937a4b37b085a25a8a6616dac373c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 19:30:59 +0000 Subject: [PATCH 13/13] Fix zsh bugs in setup-login-items from review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BLOCKER: 'local status' aborted --list mode — 'status' is a zsh read-only special var (alias for $?). Renamed to 'st'. (zsh -n can't catch this; it's a runtime, not parse, error — verified the old form dies with 'read-only variable'.) - SHOULD-FIX: whitespace trim only stripped a single leading/trailing space and missed tabs. Use extendedglob [[:space:]]## runs. Verified spaces+tabs trimmed. - Note the comma-in-display-name assumption in current_login_items. https://claude.ai/code/session_01SfjhAKQcQMoRvQRqh7QQTg --- scripts/setup/setup-login-items | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/setup/setup-login-items b/scripts/setup/setup-login-items index a401927..723f27f 100755 --- a/scripts/setup/setup-login-items +++ b/scripts/setup/setup-login-items @@ -19,6 +19,7 @@ # setup-login-items --reset-state # forget what we've bootstrapped (then re-seeds) # ═══════════════════════════════════════════════════════════════════════════════ set -uo pipefail +setopt extendedglob # for whitespace-run trimming in the conf-parse loop SCRIPT_DIR="${0:A:h}" DOT_DIR="${DOT_DIR:-${SCRIPT_DIR:h:h}}" @@ -62,6 +63,8 @@ app_path_for() { } # Current login item names (one per line). +# NB: assumes no login-item display name contains a comma (AppleScript joins the +# list with ", "). True for the curated apps; revisit if that ever changes. current_login_items() { osascript -e 'tell application "System Events" to get the name of every login item' 2>/dev/null \ | tr ',' '\n' | sed 's/^ *//;s/ *$//' @@ -89,20 +92,21 @@ hdr "Login items (curated: $CONF)" added=0 skipped=0 missing=0 while IFS='|' read -r name hidden; do - name="${name## }"; name="${name%% }" + name="${name##[[:space:]]##}"; name="${name%%[[:space:]]##}" # strip leading/trailing whitespace runs (incl. tabs) [[ -z "$name" || "${name[1]}" == "#" ]] && continue hidden="${hidden// }"; [[ -z "$hidden" ]] && hidden="true" apath="$(app_path_for "$name" || true)" if [[ "$MODE" == "list" ]]; then - local status - if [[ -z "$apath" ]]; then status="not installed" - elif in_existing "$name"; then status="login item ✓" - elif in_state "$name"; then status="removed by you (respected)" - else status="installed, NOT a login item" + # NB: don't name this 'status' — zsh reserves it (read-only alias for $?). + local st + if [[ -z "$apath" ]]; then st="not installed" + elif in_existing "$name"; then st="login item ✓" + elif in_state "$name"; then st="removed by you (respected)" + else st="installed, NOT a login item" fi - printf ' %-22s %s\n' "$name" "$status" + printf ' %-22s %s\n' "$name" "$st" continue fi