mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
1800c24d80
* feat(gemini): add Gemini CLI plan review integration Adds a new `apps/gemini-hook/` adapter that enables Plannotator plan review for Gemini CLI users via the BeforeTool hook system. The adapter reads the plan file from disk (Gemini provides a path, not inline content), delegates to the shared @plannotator/server for the browser-based review UI, and translates the decision back into Gemini's hook output format. Requires an upstream fix (google-gemini/gemini-cli#21802) that makes `decision = "allow"` user policies work for exit_plan_mode, allowing hooks to replace the built-in TUI approval dialog. Includes: - apps/gemini-hook/server/index.ts — stdin/stdout adapter - apps/gemini-hook/hooks/ — policy TOML + settings snippet - scripts/install.sh — Gemini binary download, policy install, settings config For provenance purposes, this commit was AI assisted. * refactor(gemini): use single binary with auto-detection instead of separate app Removes apps/gemini-hook/ — the plannotator binary now auto-detects Gemini CLI from stdin (plan_path = file on disk) vs Claude Code (plan = inline content) and branches input parsing + output formatting. Config fixtures live in apps/gemini/ (policy TOML + settings snippet). Install script gates on ~/.gemini existing so Claude-only users are unaffected. For provenance purposes, this commit was AI assisted. * test(gemini): add manual sandbox script for Gemini CLI integration Three modes: - --simulate: pipes BeforeTool JSON to hook, tests approve/deny output - (default): runs local patched Gemini build - --nightly: installs Gemini nightly and runs it Backs up and restores ~/.gemini config on exit. For provenance purposes, this commit was AI assisted. * feat(gemini): add slash commands, marketing tab, and docs for Gemini CLI - Add /plannotator-review and /plannotator-annotate slash commands (.toml) - Install Gemini slash commands in all three install scripts (sh, ps1, cmd) - Add Gemini tab to marketing landing page with icon - Add Gemini CLI to top-level README install section - Create apps/gemini/README.md with full setup and usage docs - Remove stale dev:gemini script and regenerate bun.lock For provenance purposes, this commit was AI assisted. * fix(gemini): merge hook into existing settings.json instead of printing instructions When ~/.gemini/settings.json already exists, use node to JSON-merge the BeforeTool hook config rather than asking the user to do it manually. Falls back to instructions only if node is unavailable. For provenance purposes, this commit was AI assisted. * fix(gemini): handle plan_filename rename and fix scoping bug Gemini CLI nightly renamed plan_path to plan_filename in exit_plan_mode. Accept both field names for forward/backward compatibility. Reconstruct full plan path from transcript_path + session_id + plans/ + filename. Also hoist planFilename variable out of try block so it's accessible in the deny output path (was causing ReferenceError). For provenance purposes, this commit was AI assisted. * fix(gemini): dim approve button when annotations exist for Gemini CLI Gemini's hook runner ignores systemMessage on the allow path, so approve-with-feedback is silently dropped — same limitation as Claude Code. Extend the existing UI gate to also apply for gemini-cli origin. For provenance purposes, this commit was AI assisted. * fix(gemini): add AGENT_CONFIG entry and fix sandbox simulate mode Register "gemini-cli" in AGENT_CONFIG so the UI shows "Gemini CLI" with proper badge styling instead of generic "Coding Agent" fallback. Update sandbox simulate mode to match production input format: use plan_filename instead of plan_path, include transcript_path, and simulate the Gemini directory structure for path reconstruction. For provenance purposes, this commit was AI assisted.
403 lines
13 KiB
Bash
403 lines
13 KiB
Bash
#!/bin/bash
|
|
set -e
|
|
|
|
REPO="backnotprop/plannotator"
|
|
INSTALL_DIR="$HOME/.local/bin"
|
|
|
|
case "$(uname -s)" in
|
|
Darwin) os="darwin" ;;
|
|
Linux) os="linux" ;;
|
|
*) echo "Unsupported OS. For Windows, run: irm https://plannotator.ai/install.ps1 | iex" >&2; exit 1 ;;
|
|
esac
|
|
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) arch="x64" ;;
|
|
arm64|aarch64) arch="arm64" ;;
|
|
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
|
|
esac
|
|
|
|
platform="${os}-${arch}"
|
|
binary_name="plannotator-${platform}"
|
|
|
|
# Clean up old Windows install locations (for users running bash on Windows)
|
|
if [ -n "$USERPROFILE" ]; then
|
|
# Running on Windows (Git Bash, MSYS, etc.) - clean up old locations
|
|
rm -f "$USERPROFILE/.local/bin/plannotator" "$USERPROFILE/.local/bin/plannotator.exe" 2>/dev/null || true
|
|
rm -f "$LOCALAPPDATA/plannotator/plannotator.exe" 2>/dev/null || true
|
|
echo "Cleaned up old Windows install locations"
|
|
fi
|
|
|
|
echo "Fetching latest version..."
|
|
latest_tag=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name"' | cut -d'"' -f4)
|
|
|
|
if [ -z "$latest_tag" ]; then
|
|
echo "Failed to fetch latest version" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Installing plannotator ${latest_tag}..."
|
|
|
|
binary_url="https://github.com/${REPO}/releases/download/${latest_tag}/${binary_name}"
|
|
checksum_url="${binary_url}.sha256"
|
|
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
tmp_file=$(mktemp)
|
|
curl -fsSL -o "$tmp_file" "$binary_url"
|
|
|
|
expected_checksum=$(curl -fsSL "$checksum_url" | cut -d' ' -f1)
|
|
|
|
if [ "$(uname -s)" = "Darwin" ]; then
|
|
actual_checksum=$(shasum -a 256 "$tmp_file" | cut -d' ' -f1)
|
|
else
|
|
actual_checksum=$(sha256sum "$tmp_file" | cut -d' ' -f1)
|
|
fi
|
|
|
|
if [ "$actual_checksum" != "$expected_checksum" ]; then
|
|
echo "Checksum verification failed!" >&2
|
|
rm -f "$tmp_file"
|
|
exit 1
|
|
fi
|
|
|
|
# Remove old binary first (handles Windows .exe and locked file issues)
|
|
rm -f "$INSTALL_DIR/plannotator" "$INSTALL_DIR/plannotator.exe" 2>/dev/null || true
|
|
|
|
mv "$tmp_file" "$INSTALL_DIR/plannotator"
|
|
chmod +x "$INSTALL_DIR/plannotator"
|
|
|
|
echo ""
|
|
echo "plannotator ${latest_tag} installed to ${INSTALL_DIR}/plannotator"
|
|
|
|
if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then
|
|
echo ""
|
|
echo "${INSTALL_DIR} is not in your PATH. Add it with:"
|
|
echo ""
|
|
|
|
case "$SHELL" in
|
|
*/zsh) shell_config="~/.zshrc" ;;
|
|
*/bash) shell_config="~/.bashrc" ;;
|
|
*) shell_config="your shell config" ;;
|
|
esac
|
|
|
|
echo " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ${shell_config}"
|
|
echo " source ${shell_config}"
|
|
fi
|
|
|
|
# Validate plugin hooks.json if plugin is already installed
|
|
PLUGIN_HOOKS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/plugins/marketplaces/plannotator/apps/hook/hooks/hooks.json"
|
|
if [ -f "$PLUGIN_HOOKS" ]; then
|
|
cat > "$PLUGIN_HOOKS" << 'HOOKS_EOF'
|
|
{
|
|
"hooks": {
|
|
"PermissionRequest": [
|
|
{
|
|
"matcher": "ExitPlanMode",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "plannotator",
|
|
"timeout": 345600
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
HOOKS_EOF
|
|
echo "Updated plugin hooks at ${PLUGIN_HOOKS}"
|
|
fi
|
|
|
|
# Clear any cached OpenCode plugin to force fresh download on next run
|
|
rm -rf "$HOME/.cache/opencode/node_modules/@plannotator" "$HOME/.bun/install/cache/@plannotator" 2>/dev/null || true
|
|
|
|
# Clear Pi jiti cache to force fresh download on next run
|
|
rm -rf /tmp/jiti 2>/dev/null || true
|
|
|
|
# Update Pi extension if pi is installed
|
|
if command -v pi &>/dev/null; then
|
|
echo "Updating Pi extension..."
|
|
pi install npm:@plannotator/pi-extension
|
|
echo "Pi extension updated."
|
|
fi
|
|
|
|
# Install /review slash command
|
|
CLAUDE_COMMANDS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/commands"
|
|
mkdir -p "$CLAUDE_COMMANDS_DIR"
|
|
|
|
cat > "$CLAUDE_COMMANDS_DIR/plannotator-review.md" << 'COMMAND_EOF'
|
|
---
|
|
description: Open interactive code review for current changes or a PR URL
|
|
allowed-tools: Bash(plannotator:*)
|
|
---
|
|
|
|
## Code Review Feedback
|
|
|
|
!`plannotator review $ARGUMENTS`
|
|
|
|
## Your task
|
|
|
|
If the review above contains feedback or annotations, address them. If no changes were requested, acknowledge and continue.
|
|
COMMAND_EOF
|
|
|
|
echo "Installed /plannotator-review command to ${CLAUDE_COMMANDS_DIR}/plannotator-review.md"
|
|
|
|
# Install /annotate slash command for Claude Code
|
|
cat > "$CLAUDE_COMMANDS_DIR/plannotator-annotate.md" << 'COMMAND_EOF'
|
|
---
|
|
description: Open interactive annotation UI for a markdown file
|
|
allowed-tools: Bash(plannotator:*)
|
|
---
|
|
|
|
## Markdown Annotations
|
|
|
|
!`plannotator annotate $ARGUMENTS`
|
|
|
|
## Your task
|
|
|
|
Address the annotation feedback above. The user has reviewed the markdown file and provided specific annotations and comments.
|
|
COMMAND_EOF
|
|
|
|
echo "Installed /plannotator-annotate command to ${CLAUDE_COMMANDS_DIR}/plannotator-annotate.md"
|
|
|
|
# Install /plannotator-last slash command for Claude Code
|
|
cat > "$CLAUDE_COMMANDS_DIR/plannotator-last.md" << 'COMMAND_EOF'
|
|
---
|
|
description: Annotate the last rendered assistant message
|
|
allowed-tools: Bash(plannotator:*)
|
|
---
|
|
|
|
## Message Annotations
|
|
|
|
!`plannotator annotate-last`
|
|
|
|
## Your task
|
|
|
|
Address the annotation feedback above. The user has reviewed your last message and provided specific annotations and comments.
|
|
COMMAND_EOF
|
|
|
|
echo "Installed /plannotator-last command to ${CLAUDE_COMMANDS_DIR}/plannotator-last.md"
|
|
|
|
# Install OpenCode slash command
|
|
OPENCODE_COMMANDS_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/command"
|
|
mkdir -p "$OPENCODE_COMMANDS_DIR"
|
|
|
|
cat > "$OPENCODE_COMMANDS_DIR/plannotator-review.md" << 'COMMAND_EOF'
|
|
---
|
|
description: Open interactive code review for current changes
|
|
---
|
|
|
|
The Plannotator Code Review has been triggered. Opening the review UI...
|
|
Acknowledge "Opening code review..." and wait for the user's feedback.
|
|
COMMAND_EOF
|
|
|
|
echo "Installed /plannotator-review command to ${OPENCODE_COMMANDS_DIR}/plannotator-review.md"
|
|
|
|
# Install /annotate slash command for OpenCode
|
|
cat > "$OPENCODE_COMMANDS_DIR/plannotator-annotate.md" << 'COMMAND_EOF'
|
|
---
|
|
description: Open interactive annotation UI for a markdown file
|
|
---
|
|
|
|
The Plannotator Annotate has been triggered. Opening the annotation UI...
|
|
Acknowledge "Opening annotation UI..." and wait for the user's feedback.
|
|
COMMAND_EOF
|
|
|
|
echo "Installed /plannotator-annotate command to ${OPENCODE_COMMANDS_DIR}/plannotator-annotate.md"
|
|
|
|
# Install /plannotator-last slash command for OpenCode
|
|
cat > "$OPENCODE_COMMANDS_DIR/plannotator-last.md" << 'COMMAND_EOF'
|
|
---
|
|
description: Annotate the last assistant message
|
|
---
|
|
COMMAND_EOF
|
|
|
|
echo "Installed /plannotator-last command to ${OPENCODE_COMMANDS_DIR}/plannotator-last.md"
|
|
|
|
# Install skills (requires git)
|
|
if command -v git &>/dev/null; then
|
|
CLAUDE_SKILLS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/skills"
|
|
AGENTS_SKILLS_DIR="$HOME/.agents/skills"
|
|
skills_tmp=$(mktemp -d)
|
|
|
|
if git clone --depth 1 --filter=blob:none --sparse \
|
|
"https://github.com/${REPO}.git" --branch "$latest_tag" "$skills_tmp/repo" 2>/dev/null && \
|
|
cd "$skills_tmp/repo" && git sparse-checkout set apps/skills 2>/dev/null; then
|
|
|
|
if [ -d "apps/skills" ] && [ "$(ls -A apps/skills 2>/dev/null)" ]; then
|
|
mkdir -p "$CLAUDE_SKILLS_DIR" "$AGENTS_SKILLS_DIR"
|
|
cp -r apps/skills/* "$CLAUDE_SKILLS_DIR/"
|
|
cp -r apps/skills/* "$AGENTS_SKILLS_DIR/"
|
|
echo "Installed skills to ${CLAUDE_SKILLS_DIR}/ and ${AGENTS_SKILLS_DIR}/"
|
|
fi
|
|
|
|
cd - >/dev/null
|
|
else
|
|
echo "Skipping skills install (git sparse-checkout failed)"
|
|
fi
|
|
|
|
rm -rf "$skills_tmp"
|
|
else
|
|
echo "Skipping skills install (git not found)"
|
|
fi
|
|
|
|
# --- Gemini CLI support (only if Gemini is installed) ---
|
|
if [ -d "$HOME/.gemini" ]; then
|
|
# Install policy file
|
|
GEMINI_POLICIES_DIR="$HOME/.gemini/policies"
|
|
mkdir -p "$GEMINI_POLICIES_DIR"
|
|
cat > "$GEMINI_POLICIES_DIR/plannotator.toml" << 'GEMINI_POLICY_EOF'
|
|
# Plannotator policy for Gemini CLI
|
|
# Allows exit_plan_mode without TUI confirmation so the browser UI is the sole gate.
|
|
[[rule]]
|
|
toolName = "exit_plan_mode"
|
|
decision = "allow"
|
|
priority = 100
|
|
GEMINI_POLICY_EOF
|
|
echo "Installed Gemini policy to ${GEMINI_POLICIES_DIR}/plannotator.toml"
|
|
|
|
# Configure hook in settings.json
|
|
GEMINI_SETTINGS="$HOME/.gemini/settings.json"
|
|
PLANNOTATOR_HOOK='{"matcher":"exit_plan_mode","hooks":[{"type":"command","command":"plannotator","timeout":345600}]}'
|
|
|
|
if [ -f "$GEMINI_SETTINGS" ]; then
|
|
if ! grep -q '"plannotator"' "$GEMINI_SETTINGS" 2>/dev/null; then
|
|
# Merge hook into existing settings.json using node (ships with Gemini CLI)
|
|
if command -v node &>/dev/null; then
|
|
node -e "
|
|
const fs = require('fs');
|
|
const settings = JSON.parse(fs.readFileSync('$GEMINI_SETTINGS', 'utf8'));
|
|
if (!settings.hooks) settings.hooks = {};
|
|
if (!settings.hooks.BeforeTool) settings.hooks.BeforeTool = [];
|
|
settings.hooks.BeforeTool.push($PLANNOTATOR_HOOK);
|
|
fs.writeFileSync('$GEMINI_SETTINGS', JSON.stringify(settings, null, 2) + '\n');
|
|
"
|
|
echo "Added plannotator hook to ${GEMINI_SETTINGS}"
|
|
else
|
|
echo ""
|
|
echo "Add the following to your ~/.gemini/settings.json hooks:"
|
|
echo ""
|
|
echo ' "hooks": {'
|
|
echo ' "BeforeTool": [{'
|
|
echo ' "matcher": "exit_plan_mode",'
|
|
echo ' "hooks": [{"type": "command", "command": "plannotator", "timeout": 345600}]'
|
|
echo ' }]'
|
|
echo ' }'
|
|
fi
|
|
fi
|
|
else
|
|
cat > "$GEMINI_SETTINGS" << 'GEMINI_SETTINGS_EOF'
|
|
{
|
|
"hooks": {
|
|
"BeforeTool": [
|
|
{
|
|
"matcher": "exit_plan_mode",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "plannotator",
|
|
"timeout": 345600
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"experimental": {
|
|
"plan": true
|
|
}
|
|
}
|
|
GEMINI_SETTINGS_EOF
|
|
echo "Created Gemini settings at ${GEMINI_SETTINGS}"
|
|
fi
|
|
|
|
# Install slash commands
|
|
GEMINI_COMMANDS_DIR="$HOME/.gemini/commands"
|
|
mkdir -p "$GEMINI_COMMANDS_DIR"
|
|
|
|
cat > "$GEMINI_COMMANDS_DIR/plannotator-review.toml" << 'GEMINI_CMD_EOF'
|
|
description = "Open interactive code review for current changes or a PR URL"
|
|
prompt = """
|
|
## Code Review Feedback
|
|
|
|
!{plannotator review {{args}}}
|
|
|
|
## Your task
|
|
|
|
If the review above contains feedback or annotations, address them. If no changes were requested, acknowledge and continue.
|
|
"""
|
|
GEMINI_CMD_EOF
|
|
|
|
cat > "$GEMINI_COMMANDS_DIR/plannotator-annotate.toml" << 'GEMINI_CMD_EOF'
|
|
description = "Open interactive annotation UI for a markdown file or folder"
|
|
prompt = """
|
|
## Markdown Annotations
|
|
|
|
!{plannotator annotate {{args}}}
|
|
|
|
## Your task
|
|
|
|
Address the annotation feedback above. The user has reviewed the markdown file and provided specific annotations and comments.
|
|
"""
|
|
GEMINI_CMD_EOF
|
|
|
|
echo "Installed Gemini slash commands to ${GEMINI_COMMANDS_DIR}/"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " OPENCODE USERS"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Add the plugin to your opencode.json:"
|
|
echo ""
|
|
echo ' "plugin": ["@plannotator/opencode@latest"]'
|
|
echo ""
|
|
echo "Then restart OpenCode. The /plannotator-review, /plannotator-annotate, and /plannotator-last commands are ready!"
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " PI USERS"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Install or update the extension:"
|
|
echo ""
|
|
echo " pi install npm:@plannotator/pi-extension"
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " GEMINI CLI USERS"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Enable plan mode in Gemini settings, then run:"
|
|
echo ""
|
|
echo " gemini"
|
|
echo " /plan"
|
|
echo ""
|
|
echo "Plans will open in your browser for review."
|
|
echo "If settings.json was not auto-configured, see:"
|
|
echo " ~/.gemini/settings.json (add BeforeTool hook)"
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " CLAUDE CODE USERS: YOU'RE ALL SET!"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Install the Claude Code plugin:"
|
|
echo " /plugin marketplace add backnotprop/plannotator"
|
|
echo " /plugin install plannotator@plannotator"
|
|
echo ""
|
|
echo "The /plannotator-review, /plannotator-annotate, and /plannotator-last commands are ready to use after you restart Claude Code!"
|
|
|
|
# Warn if plannotator is configured in both settings.json hooks AND the plugin (causes double execution)
|
|
# Only warn when the plugin is installed — manual-only users won't have overlap
|
|
CLAUDE_SETTINGS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"
|
|
if [ -f "$PLUGIN_HOOKS" ] && [ -f "$CLAUDE_SETTINGS" ] && grep -q '"command".*plannotator' "$CLAUDE_SETTINGS" 2>/dev/null; then
|
|
echo ""
|
|
echo "⚠️ ⚠️ ⚠️ WARNING: DUPLICATE HOOK DETECTED ⚠️ ⚠️ ⚠️"
|
|
echo ""
|
|
echo " plannotator was found in your settings.json hooks:"
|
|
echo " $CLAUDE_SETTINGS"
|
|
echo ""
|
|
echo " This will cause plannotator to run TWICE on each plan review."
|
|
echo " Remove the plannotator hook from settings.json and rely on the"
|
|
echo " plugin instead (installed automatically via marketplace)."
|
|
echo ""
|
|
echo "⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️"
|
|
fi
|