From abd241fcf1a53fcd04bdfb6fd3bb99074d8df4f0 Mon Sep 17 00:00:00 2001 From: Orr Gottlieb Date: Wed, 11 Feb 2026 14:44:22 +0200 Subject: [PATCH 1/3] Add interactive setup script and recommended settings - Add setup.sh: Interactive installer with checkbox menus for skills, commands, and hooks - Supports --all flag for non-interactive installation - Includes --uninstall mode for clean removal - Automatically configures settings.json with SessionStart hooks - Optional gemini-image script installation with PATH and API key detection - Add settings.json: Complete recommended configuration example - Model set to claude-opus-4-6 - SessionStart hooks with proper matcher/nested hooks structure - Status line using @owloops/claude-powerline via npx - Update README.md: Improve settings documentation - Expand Hook Registration section into comprehensive Settings section - Document status line configuration - Add subsections for better organization Co-Authored-By: Claude Sonnet 4.5 --- README.md | 34 ++- settings.json | 24 ++ setup.sh | 652 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 702 insertions(+), 8 deletions(-) create mode 100644 settings.json create mode 100755 setup.sh diff --git a/README.md b/README.md index 8b5175b..afea4ee 100644 --- a/README.md +++ b/README.md @@ -78,27 +78,45 @@ Set your Gemini API key as an environment variable: export GEMINI_API_KEY="your-api-key-here" ``` -## Hook Registration +## Settings -Hooks need to be registered in your `~/.claude/settings.json` to be triggered. Add them under the `hooks` key: +Below is the recommended `~/.claude/settings.json` configuration. It includes the model, hook registration, and status line: ```json { + "model": "claude-opus-4-6", "hooks": { "SessionStart": [ { - "type": "command", - "command": "~/.claude/hooks/show-session-tips.sh" - }, - { - "type": "command", - "command": "~/.claude/hooks/check-context-freshness.sh" + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "~/.claude/hooks/show-session-tips.sh" + }, + { + "type": "command", + "command": "~/.claude/hooks/check-context-freshness.sh" + } + ] } ] + }, + "statusLine": { + "type": "command", + "command": "npx -y @owloops/claude-powerline --style=powerline" } } ``` +### Hook Registration + +Hooks need to be registered under the `hooks` key with a `matcher` (empty string matches all projects) and a nested `hooks` array. + +### Status Line + +The `statusLine` setting configures a persistent status bar using [`@owloops/claude-powerline`](https://www.npmjs.com/package/@owloops/claude-powerline). It runs via `npx` so no global install is required. + ## Skills Not Included Some skills referenced in the documentation are not included in this public subset: diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..1a36540 --- /dev/null +++ b/settings.json @@ -0,0 +1,24 @@ +{ + "model": "claude-opus-4-6", + "hooks": { + "SessionStart": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "~/.claude/hooks/show-session-tips.sh" + }, + { + "type": "command", + "command": "~/.claude/hooks/check-context-freshness.sh" + } + ] + } + ] + }, + "statusLine": { + "type": "command", + "command": "npx -y @owloops/claude-powerline --style=powerline" + } +} diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..6c988c5 --- /dev/null +++ b/setup.sh @@ -0,0 +1,652 @@ +#!/bin/bash +set -euo pipefail + +# ─── Config ────────────────────────────────────────────────────────────────── +REPO_DIR="$(cd "$(dirname "$0")" && pwd)" +CLAUDE_DIR="$HOME/.claude" +INSTALL_GEMINI="" +INSTALL_ALL="" +UNINSTALL="" + +# ─── Colors ────────────────────────────────────────────────────────────────── +info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$1"; } +success() { printf '\033[1;32m[ OK ]\033[0m %s\n' "$1"; } +skip() { printf '\033[1;33m[SKIP]\033[0m %s\n' "$1"; } +error() { printf '\033[1;31m[ERR ]\033[0m %s\n' "$1" >&2; } + +# ─── Parse args ────────────────────────────────────────────────────────────── +for arg in "$@"; do + case "$arg" in + --all) INSTALL_ALL="yes"; INSTALL_GEMINI="${INSTALL_GEMINI:-yes}" ;; + --with-gemini) INSTALL_GEMINI="yes" ;; + --no-gemini) INSTALL_GEMINI="no" ;; + --uninstall) UNINSTALL="yes" ;; + --help|-h) + cat <&2 + exit 1 + ;; + esac +done + +# ─── Prerequisites ─────────────────────────────────────────────────────────── +if [[ ! -d "$CLAUDE_DIR" ]]; then + error "$CLAUDE_DIR does not exist. Please run Claude Code at least once first." + exit 1 +fi + +if ! command -v jq &>/dev/null; then + error "jq is required but not installed. Install it with: brew install jq" + exit 1 +fi + +echo "" + +# ─── Checkbox menu ─────────────────────────────────────────────────────────── +# Reads from global arrays: _sel_names[@] and _sel_existing[@] +# Sets SELECTED_INDICES to the array of chosen indices (0-based) +# +# Usage: checkbox_menu
[uninstall] +# install (default): new items are selectable, existing items are dimmed +# uninstall: existing items are selectable, missing items are dimmed +checkbox_menu() { + local section="$1" + local mode="${2:-install}" + SELECTED_INDICES=() + + local count=${#_sel_names[@]} + local cursor=0 + local checked=() + local available=() + local first_available=-1 + + # Labels and colors differ by mode + local dimmed_label="already installed" + local empty_msg="All $(echo "$section" | tr '[:upper:]' '[:lower:]') already installed." + local batch_msg="Installing all (--all)" + local batch_color="\033[1;32m" # green + if [[ "$mode" == "uninstall" ]]; then + dimmed_label="not installed" + empty_msg="No $(echo "$section" | tr '[:upper:]' '[:lower:]') to uninstall." + batch_msg="Removing all (--all)" + batch_color="\033[1;31m" # red + fi + + # Initialize state — available items depend on mode + for i in $(seq 0 $((count - 1))); do + local is_existing="${_sel_existing[$i]}" + local is_available="no" + if [[ "$mode" == "uninstall" && "$is_existing" == "yes" ]] \ + || [[ "$mode" == "install" && "$is_existing" == "no" ]]; then + is_available="yes" + fi + + if [[ "$is_available" == "yes" ]]; then + checked+=("yes") + available+=("yes") + if [[ $first_available -eq -1 ]]; then + first_available=$i + fi + else + checked+=("no") + available+=("no") + fi + done + + echo "" + echo "─── $section ────────────────────────────────────────" + + # Nothing actionable + if [[ $first_available -eq -1 ]]; then + echo "" + for i in $(seq 0 $((count - 1))); do + printf " \033[2m %s (%s)\033[0m\n" "${_sel_names[$i]}" "$dimmed_label" + done + echo "" + info "$empty_msg" + return + fi + + cursor=$first_available + + # Non-interactive mode + if [[ "$INSTALL_ALL" == "yes" ]]; then + echo "" + for i in $(seq 0 $((count - 1))); do + if [[ "${available[$i]}" == "yes" ]]; then + printf " ${batch_color}[x]\033[0m %s\n" "${_sel_names[$i]}" + SELECTED_INDICES+=("$i") + else + printf " \033[2m %s (%s)\033[0m\n" "${_sel_names[$i]}" "$dimmed_label" + fi + done + echo "" + info "$batch_msg" + return + fi + + # ── Render function ── + local rendered_lines=$((count + 1)) # items + hint line + + render_menu() { + local is_redraw="$1" + + # Move cursor up to overwrite previous render + if [[ "$is_redraw" == "yes" ]]; then + printf '\033[%dA' "$rendered_lines" + fi + + for i in $(seq 0 $((count - 1))); do + printf '\033[2K' # clear line + if [[ "${available[$i]}" == "no" ]]; then + printf " \033[2m %s (%s)\033[0m\n" "${_sel_names[$i]}" "$dimmed_label" + else + local marker=" " + local color_start="" + local color_end="" + if [[ $i -eq $cursor ]]; then + marker="❯ " + color_start="\033[1;36m" + color_end="\033[0m" + fi + + local box="[ ]" + if [[ "${checked[$i]}" == "yes" ]]; then + box="[x]" + fi + + printf "${color_start}${marker}${box} %s${color_end}\n" "${_sel_names[$i]}" + fi + done + + # Hint line + printf '\033[2K' + printf " \033[2m↑↓ move space toggle a all n none enter confirm\033[0m\n" + } + + # Initial render + echo "" + render_menu "no" + + # Hide cursor + tput civis 2>/dev/null || true + # Ensure cursor is restored on exit/interrupt + trap 'tput cnorm 2>/dev/null || true' RETURN + + # ── Input loop ── + while true; do + IFS= read -rsn1 key + + case "$key" in + $'\x1b') + # Read rest of escape sequence with timeout + local seq="" + IFS= read -rsn2 -t 1 seq 2>/dev/null || true + case "$seq" in + '[A') # Up arrow — find previous available + local new=$cursor + local i=$((cursor - 1)) + while [[ $i -ge 0 ]]; do + if [[ "${available[$i]}" == "yes" ]]; then + new=$i + break + fi + i=$((i - 1)) + done + cursor=$new + ;; + '[B') # Down arrow — find next available + local new=$cursor + local i=$((cursor + 1)) + while [[ $i -lt $count ]]; do + if [[ "${available[$i]}" == "yes" ]]; then + new=$i + break + fi + i=$((i + 1)) + done + cursor=$new + ;; + esac + render_menu "yes" + ;; + ' ') # Space — toggle current item + if [[ "${checked[$cursor]}" == "yes" ]]; then + checked[$cursor]="no" + else + checked[$cursor]="yes" + fi + render_menu "yes" + ;; + 'a'|'A') # Select all + for i in $(seq 0 $((count - 1))); do + if [[ "${available[$i]}" == "yes" ]]; then + checked[$i]="yes" + fi + done + render_menu "yes" + ;; + 'n'|'N') # Deselect all + for i in $(seq 0 $((count - 1))); do + if [[ "${available[$i]}" == "yes" ]]; then + checked[$i]="no" + fi + done + render_menu "yes" + ;; + '') # Enter — confirm + break + ;; + esac + done + + # Restore cursor + tput cnorm 2>/dev/null || true + + # Collect selected indices + for i in $(seq 0 $((count - 1))); do + if [[ "${checked[$i]}" == "yes" ]]; then + SELECTED_INDICES+=("$i") + fi + done +} + +# ─── Uninstall mode ───────────────────────────────────────────────────────── +if [[ "$UNINSTALL" == "yes" ]]; then + info "Uninstalling Claude Code setup" + removed=() + + # ── Skills ── + _sel_names=() + _sel_existing=() + for skill_dir in "$REPO_DIR"/skills/*/; do + name="$(basename "$skill_dir")" + _sel_names+=("$name") + target="$CLAUDE_DIR/skills/$name" + if [[ -d "$target" || -L "$target" ]]; then + _sel_existing+=("yes") + else + _sel_existing+=("no") + fi + done + + checkbox_menu "Skills" "uninstall" + + if [[ ${#SELECTED_INDICES[@]} -gt 0 ]]; then + echo "" + for idx in "${SELECTED_INDICES[@]}"; do + rm -rf "$CLAUDE_DIR/skills/${_sel_names[$idx]}" + success "Removed skill: ${_sel_names[$idx]}" + removed+=("skill:${_sel_names[$idx]}") + done + fi + + # ── Commands ── + _sel_names=() + _sel_existing=() + for cmd_file in "$REPO_DIR"/commands/*.md; do + name="$(basename "$cmd_file" .md)" + _sel_names+=("$name") + target="$CLAUDE_DIR/commands/$(basename "$cmd_file")" + if [[ -f "$target" ]]; then + _sel_existing+=("yes") + else + _sel_existing+=("no") + fi + done + + checkbox_menu "Commands" "uninstall" + + if [[ ${#SELECTED_INDICES[@]} -gt 0 ]]; then + echo "" + for idx in "${SELECTED_INDICES[@]}"; do + rm -f "$CLAUDE_DIR/commands/${_sel_names[$idx]}.md" + success "Removed command: ${_sel_names[$idx]}" + removed+=("command:${_sel_names[$idx]}") + done + fi + + # ── Hooks ── + _sel_names=() + _sel_existing=() + for hook_file in "$REPO_DIR"/hooks/*.sh; do + name="$(basename "$hook_file")" + _sel_names+=("$name") + target="$CLAUDE_DIR/hooks/$name" + if [[ -f "$target" ]]; then + _sel_existing+=("yes") + else + _sel_existing+=("no") + fi + done + + checkbox_menu "Hooks" "uninstall" + + if [[ ${#SELECTED_INDICES[@]} -gt 0 ]]; then + echo "" + for idx in "${SELECTED_INDICES[@]}"; do + rm -f "$CLAUDE_DIR/hooks/${_sel_names[$idx]}" + success "Removed hook: ${_sel_names[$idx]}" + removed+=("hook:${_sel_names[$idx]}") + done + fi + + # ── Settings: SessionStart hooks ── + echo "" + echo "─── Settings ─────────────────────────────────────────" + echo "" + settings_file="$CLAUDE_DIR/settings.json" + + has_session_start=$(jq -e '.hooks.SessionStart // empty | length > 0' "$settings_file" >/dev/null 2>&1 && echo "yes" || echo "no") + + if [[ "$has_session_start" == "no" ]]; then + skip "settings.json: No SessionStart hooks to remove" + else + remove_hooks="yes" + + if [[ "$INSTALL_ALL" != "yes" ]]; then + printf '\033[1;34m >\033[0m Remove SessionStart hooks from settings.json? (\033[1mY\033[0m/n): ' + read -r answer + case "$answer" in + [nN]|[nN][oO]) remove_hooks="no" ;; + *) remove_hooks="yes" ;; + esac + fi + + if [[ "$remove_hooks" == "yes" ]]; then + cp "$settings_file" "$settings_file.backup" + info "Backed up settings.json to settings.json.backup" + + tmp_file=$(mktemp) + jq 'del(.hooks.SessionStart)' "$settings_file" > "$tmp_file" + mv "$tmp_file" "$settings_file" + + success "settings.json: Removed SessionStart hooks" + removed+=("settings:SessionStart") + else + skip "settings.json: SessionStart hooks (kept)" + fi + fi + + # ── Scripts: gemini-image ── + echo "" + echo "─── Scripts ──────────────────────────────────────────" + echo "" + gemini_target="$HOME/scripts/gemini-image" + + if [[ ! -f "$gemini_target" ]]; then + skip "gemini-image (not installed)" + else + remove_gemini="yes" + + if [[ "$INSTALL_ALL" != "yes" ]]; then + printf '\033[1;34m >\033[0m Remove gemini-image from ~/scripts/? (y/\033[1mN\033[0m): ' + read -r answer + case "$answer" in + [yY]|[yY][eE][sS]) remove_gemini="yes" ;; + *) remove_gemini="no" ;; + esac + fi + + if [[ "$remove_gemini" == "yes" ]]; then + rm -f "$gemini_target" + success "Removed gemini-image from $gemini_target" + removed+=("script:gemini-image") + else + skip "gemini-image (kept)" + fi + fi + + # ── Summary ── + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + if [[ ${#removed[@]} -gt 0 ]]; then + success "Removed ${#removed[@]} item(s): ${removed[*]}" + else + info "Nothing to remove." + fi + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + exit 0 +fi + +info "Installing Claude Code setup from $REPO_DIR" + +# ─── Tracking ──────────────────────────────────────────────────────────────── +installed=() +skipped=() + +# ─── Collect skills ────────────────────────────────────────────────────────── +_sel_names=() +skill_dirs=() +_sel_existing=() + +for skill_dir in "$REPO_DIR"/skills/*/; do + name="$(basename "$skill_dir")" + _sel_names+=("$name") + skill_dirs+=("$skill_dir") + target="$CLAUDE_DIR/skills/$name" + if [[ -d "$target" || -L "$target" ]]; then + _sel_existing+=("yes") + else + _sel_existing+=("no") + fi +done + +checkbox_menu "Skills" + +if [[ ${#SELECTED_INDICES[@]} -gt 0 ]]; then + mkdir -p "$CLAUDE_DIR/skills" + echo "" + for idx in "${SELECTED_INDICES[@]}"; do + cp -R "${skill_dirs[$idx]}" "$CLAUDE_DIR/skills/${_sel_names[$idx]}" + success "Skill: ${_sel_names[$idx]}" + installed+=("skill:${_sel_names[$idx]}") + done +fi + +for i in $(seq 0 $((${#_sel_names[@]} - 1))); do + if [[ "${_sel_existing[$i]}" == "yes" ]]; then + skipped+=("skill:${_sel_names[$i]}") + fi +done + +# ─── Collect commands ──────────────────────────────────────────────────────── +_sel_names=() +cmd_files=() +_sel_existing=() + +for cmd_file in "$REPO_DIR"/commands/*.md; do + name="$(basename "$cmd_file" .md)" + _sel_names+=("$name") + cmd_files+=("$cmd_file") + target="$CLAUDE_DIR/commands/$(basename "$cmd_file")" + if [[ -f "$target" ]]; then + _sel_existing+=("yes") + else + _sel_existing+=("no") + fi +done + +checkbox_menu "Commands" + +if [[ ${#SELECTED_INDICES[@]} -gt 0 ]]; then + mkdir -p "$CLAUDE_DIR/commands" + echo "" + for idx in "${SELECTED_INDICES[@]}"; do + cp "${cmd_files[$idx]}" "$CLAUDE_DIR/commands/$(basename "${cmd_files[$idx]}")" + success "Command: ${_sel_names[$idx]}" + installed+=("command:${_sel_names[$idx]}") + done +fi + +for i in $(seq 0 $((${#_sel_names[@]} - 1))); do + if [[ "${_sel_existing[$i]}" == "yes" ]]; then + skipped+=("command:${_sel_names[$i]}") + fi +done + +# ─── Collect hooks ─────────────────────────────────────────────────────────── +_sel_names=() +hook_files=() +_sel_existing=() + +for hook_file in "$REPO_DIR"/hooks/*.sh; do + name="$(basename "$hook_file")" + _sel_names+=("$name") + hook_files+=("$hook_file") + target="$CLAUDE_DIR/hooks/$name" + if [[ -f "$target" ]]; then + _sel_existing+=("yes") + else + _sel_existing+=("no") + fi +done + +checkbox_menu "Hooks" + +if [[ ${#SELECTED_INDICES[@]} -gt 0 ]]; then + mkdir -p "$CLAUDE_DIR/hooks" + echo "" + for idx in "${SELECTED_INDICES[@]}"; do + cp "${hook_files[$idx]}" "$CLAUDE_DIR/hooks/${_sel_names[$idx]}" + chmod +x "$CLAUDE_DIR/hooks/${_sel_names[$idx]}" + success "Hook: ${_sel_names[$idx]}" + installed+=("hook:${_sel_names[$idx]}") + done +fi + +for i in $(seq 0 $((${#_sel_names[@]} - 1))); do + if [[ "${_sel_existing[$i]}" == "yes" ]]; then + skipped+=("hook:${_sel_names[$i]}") + fi +done + +# ─── Configure settings.json ───────────────────────────────────────────────── +echo "" +echo "─── Settings ─────────────────────────────────────────" +echo "" +settings_file="$CLAUDE_DIR/settings.json" + +if [[ ! -f "$settings_file" ]]; then + echo '{}' > "$settings_file" +fi + +has_session_start=$(jq -e '.hooks.SessionStart // empty | length > 0' "$settings_file" >/dev/null 2>&1 && echo "yes" || echo "no") + +if [[ "$has_session_start" == "yes" ]]; then + skip "settings.json: SessionStart hooks already configured" + skipped+=("settings:SessionStart") +else + register_hooks="yes" + + if [[ "$INSTALL_ALL" != "yes" ]]; then + printf '\033[1;34m >\033[0m Register SessionStart hooks in settings.json? (\033[1mY\033[0m/n): ' + read -r answer + case "$answer" in + [nN]|[nN][oO]) register_hooks="no" ;; + *) register_hooks="yes" ;; + esac + fi + + if [[ "$register_hooks" == "yes" ]]; then + cp "$settings_file" "$settings_file.backup" + info "Backed up settings.json to settings.json.backup" + + tmp_file=$(mktemp) + jq '.hooks.SessionStart = [ + { + "matcher": "", + "hooks": [ + {"type": "command", "command": "~/.claude/hooks/show-session-tips.sh"}, + {"type": "command", "command": "~/.claude/hooks/check-context-freshness.sh"} + ] + } + ]' "$settings_file" > "$tmp_file" + mv "$tmp_file" "$settings_file" + + success "settings.json: Added SessionStart hooks" + installed+=("settings:SessionStart") + else + skip "settings.json: SessionStart hooks (not requested)" + fi +fi + +# ─── Optional: gemini-image script ─────────────────────────────────────────── +echo "" +echo "─── Scripts ──────────────────────────────────────────" +echo "" + +gemini_target="$HOME/scripts/gemini-image" + +if [[ -f "$gemini_target" ]]; then + skip "gemini-image (already exists at $gemini_target)" + skipped+=("script:gemini-image") +else + if [[ -z "$INSTALL_GEMINI" ]]; then + printf '\033[1;34m >\033[0m Install gemini-image script to ~/scripts/? (y/\033[1mN\033[0m): ' + read -r answer + case "$answer" in + [yY]|[yY][eE][sS]) INSTALL_GEMINI="yes" ;; + *) INSTALL_GEMINI="no" ;; + esac + fi + + if [[ "$INSTALL_GEMINI" == "yes" ]]; then + mkdir -p "$HOME/scripts" + cp "$REPO_DIR/scripts/gemini-image" "$gemini_target" + chmod +x "$gemini_target" + success "gemini-image installed to $gemini_target" + installed+=("script:gemini-image") + + if [[ -z "${GEMINI_API_KEY:-}" ]]; then + echo "" + info "Note: GEMINI_API_KEY is not set. Set it in your shell profile:" + echo " export GEMINI_API_KEY=\"your-api-key-here\"" + fi + if ! echo "$PATH" | tr ':' '\n' | grep -qx "$HOME/scripts"; then + echo "" + info "Note: ~/scripts is not in your PATH. Add it to your shell profile:" + echo " export PATH=\"\$HOME/scripts:\$PATH\"" + fi + else + skip "gemini-image (not requested)" + fi +fi + +# ─── Summary ───────────────────────────────────────────────────────────────── +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +if [[ ${#installed[@]} -gt 0 ]]; then + success "Installed ${#installed[@]} item(s): ${installed[*]}" +else + info "Nothing new to install." +fi +if [[ ${#skipped[@]} -gt 0 ]]; then + skip "Skipped ${#skipped[@]} item(s) (already present)" +fi +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" From 4a826f31d91336e3ec3f060c701c059375eb4aa0 Mon Sep 17 00:00:00 2001 From: Orr Gottlieb Date: Wed, 11 Feb 2026 14:53:19 +0200 Subject: [PATCH 2/3] Remove settings.json and simplify README configuration - Remove settings.json file (not needed in repo) - Remove model configuration from README (user preference) - Remove statusLine configuration from README (user preference) - Simplify Settings section back to Hook Registration only - Keep hook registration example with proper matcher/nested hooks structure Co-Authored-By: Claude Sonnet 4.5 --- README.md | 17 +++-------------- settings.json | 24 ------------------------ 2 files changed, 3 insertions(+), 38 deletions(-) delete mode 100644 settings.json diff --git a/README.md b/README.md index afea4ee..34afcff 100644 --- a/README.md +++ b/README.md @@ -78,13 +78,12 @@ Set your Gemini API key as an environment variable: export GEMINI_API_KEY="your-api-key-here" ``` -## Settings +## Hook Registration -Below is the recommended `~/.claude/settings.json` configuration. It includes the model, hook registration, and status line: +Hooks need to be registered in your `~/.claude/settings.json` to be triggered. Add them under the `hooks` key: ```json { - "model": "claude-opus-4-6", "hooks": { "SessionStart": [ { @@ -101,21 +100,11 @@ Below is the recommended `~/.claude/settings.json` configuration. It includes th ] } ] - }, - "statusLine": { - "type": "command", - "command": "npx -y @owloops/claude-powerline --style=powerline" } } ``` -### Hook Registration - -Hooks need to be registered under the `hooks` key with a `matcher` (empty string matches all projects) and a nested `hooks` array. - -### Status Line - -The `statusLine` setting configures a persistent status bar using [`@owloops/claude-powerline`](https://www.npmjs.com/package/@owloops/claude-powerline). It runs via `npx` so no global install is required. +Hooks are registered under the `hooks` key with a `matcher` (empty string matches all projects) and a nested `hooks` array. ## Skills Not Included diff --git a/settings.json b/settings.json deleted file mode 100644 index 1a36540..0000000 --- a/settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "model": "claude-opus-4-6", - "hooks": { - "SessionStart": [ - { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "~/.claude/hooks/show-session-tips.sh" - }, - { - "type": "command", - "command": "~/.claude/hooks/check-context-freshness.sh" - } - ] - } - ] - }, - "statusLine": { - "type": "command", - "command": "npx -y @owloops/claude-powerline --style=powerline" - } -} From 21b8521d03abf886ecd099603bbd939dd7c291c3 Mon Sep 17 00:00:00 2001 From: Orr Gottlieb Date: Wed, 11 Feb 2026 14:53:59 +0200 Subject: [PATCH 3/3] Update README to use setup.sh for installation - Add Interactive Setup section as the recommended installation method - Document setup.sh features: checkbox menus, keyboard shortcuts, auto-configuration - Add Non-Interactive Installation section with --all flag examples - Add Uninstall section documenting --uninstall flag - Move manual installation to separate section for users who prefer it - Reorganize installation documentation for better flow Co-Authored-By: Claude Sonnet 4.5 --- README.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 34afcff..a0ff55a 100644 --- a/README.md +++ b/README.md @@ -49,24 +49,65 @@ Hooks are shell scripts triggered at specific Claude Code lifecycle events. ## Installation -Copy the directories you want into your `~/.claude/` directory: +### Interactive Setup (Recommended) + +Clone the repository and run the interactive setup script: ```bash # Clone this repo git clone https://github.com/omril321/claude-code-setup.git +cd claude-code-setup + +# Run interactive setup +./setup.sh +``` + +The setup script provides: +- **Interactive checkbox menus** to select which skills, commands, and hooks to install +- **Automatic settings.json configuration** for SessionStart hooks +- **Optional gemini-image script installation** with PATH and API key detection +- **Keyboard shortcuts**: `↑↓` to navigate, `space` to toggle, `a` to select all, `n` to deselect all, `enter` to confirm + +### Non-Interactive Installation + +Install everything without prompts: +```bash +./setup.sh --all +``` + +Or skip the gemini-image script: + +```bash +./setup.sh --all --no-gemini +``` + +### Uninstall + +Remove previously installed items: + +```bash +./setup.sh --uninstall +``` + +### Manual Installation + +If you prefer manual installation: + +```bash # Copy skills -cp -R claude-code-setup/skills/* ~/.claude/skills/ +cp -R skills/* ~/.claude/skills/ # Copy commands -cp -R claude-code-setup/commands/* ~/.claude/commands/ +cp -R commands/* ~/.claude/commands/ # Copy hooks -cp -R claude-code-setup/hooks/* ~/.claude/hooks/ +cp -R hooks/* ~/.claude/hooks/ +chmod +x ~/.claude/hooks/*.sh # Copy scripts (optional - for generate-image skill) mkdir -p ~/scripts -cp claude-code-setup/scripts/gemini-image ~/scripts/ +cp scripts/gemini-image ~/scripts/ chmod +x ~/scripts/gemini-image ```