diff --git a/GEMINI.md b/GEMINI.md deleted file mode 100644 index dc293dc8..00000000 --- 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 4bc990e4..bbde43fd 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 eaae5c28..de3ae9b8 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 2378d320..936c61f0 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. + ## Secrets Awareness - API keys are scoped per-project via direnv `.envrc`, NOT globally exported diff --git a/config.sh b/config.sh index 20dc5616..0187b10e 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, zerobrew|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 cfcbc4df..26178266 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 29f8ee91..90e8f90e 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/custom_bins/update-ai-tools b/custom_bins/update-ai-tools index 840ed30e..65c360c2 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, 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,8 +187,9 @@ log "PATH: $PATH" set +e update_claude -update_tool "gemini" "gemini-cli" "@google/gemini-cli" -update_tool "codex" "codex" "@openai/codex" +update_antigravity +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 12078299..00000000 --- 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 817f02a2..e2edecb1 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) @@ -230,7 +230,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 @@ -241,19 +241,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" - 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" + "codex|install_codex_cli" \ + "opencode|install_opencode" \ + "antigravity|install_antigravity_cli" fi # MCP servers (sequential — unclear if concurrent-safe) diff --git a/scripts/cleanup/setup_ai_update.sh b/scripts/cleanup/setup_ai_update.sh index 8ea5f57e..f1f8715b 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, Antigravity CLI, 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 72def994..bfba3b1e 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 38f38c13..efe684fc 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 00000000..a165609a --- /dev/null +++ b/scripts/sync_claude_to_antigravity.sh @@ -0,0 +1,285 @@ +#!/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. +# +# 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" + +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" + +# ---------- 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." diff --git a/scripts/sync_claude_to_gemini.sh b/scripts/sync_claude_to_gemini.sh deleted file mode 100755 index 51fe677c..00000000 --- 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