gitbasher 5.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +32 -16
  3. package/dist/gitb +350 -147
  4. package/package.json +1 -1
package/dist/gitb CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  set -o pipefail
3
- GITBASHER_VERSION="5.0.1"
3
+ GITBASHER_VERSION="5.1.0"
4
4
  if [ "$1" == "init" ] || [ "$1" == "i" ]; then
5
5
  git init
6
6
  fi
@@ -839,29 +839,7 @@ function config_source_tag {
839
839
  default) echo -e "${GRAY}(default)${ENDCOLOR}" ;;
840
840
  esac
841
841
  }
842
- function print_configuration {
843
- echo -e "${YELLOW}Current configuration:${ENDCOLOR}"
844
- local user_name=$(get_config_value user.name)
845
- if [ -n "$user_name" ]; then
846
- echo -e "\tuser.name:\t${YELLOW}$user_name${ENDCOLOR} $(config_source_tag user.name)"
847
- else
848
- echo -e "\tuser.name:\t${RED}not set${ENDCOLOR}"
849
- fi
850
- local user_email=$(get_config_value user.email)
851
- if [ -n "$user_email" ]; then
852
- echo -e "\tuser.email:\t${YELLOW}$user_email${ENDCOLOR} $(config_source_tag user.email)"
853
- else
854
- echo -e "\tuser.email:\t${RED}not set${ENDCOLOR}"
855
- fi
856
- echo -e "\tdefault:\t${YELLOW}$main_branch${ENDCOLOR} $(config_source_tag gitbasher.branch)"
857
- echo -e "\tseparator:\t${YELLOW}$sep${ENDCOLOR} $(config_source_tag gitbasher.sep)"
858
- echo -e "\teditor:\t\t${YELLOW}$editor${ENDCOLOR} $(config_source_tag core.editor)"
859
- if [ "$ticket_name" != "" ]; then
860
- echo -e "\tticket:\t\t${YELLOW}$ticket_name${ENDCOLOR} $(config_source_tag gitbasher.ticket)"
861
- fi
862
- if [ "$scopes" != "" ]; then
863
- echo -e "\tscopes:\t\t${YELLOW}$scopes${ENDCOLOR} $(config_source_tag gitbasher.scopes)"
864
- fi
842
+ function print_ai_configuration {
865
843
  local ai_provider=$(get_ai_provider)
866
844
  echo -e "\tAI provider:\t${GREEN}$ai_provider${ENDCOLOR} $(config_source_tag gitbasher.ai-provider)"
867
845
  local ai_base_url=$(get_ai_base_url)
@@ -903,6 +881,31 @@ function print_configuration {
903
881
  echo -e "\t\t\t${GREEN}$(get_ai_model_for full)${ENDCOLOR} ${GRAY}(full)${ENDCOLOR} $(config_source_tag gitbasher.ai-model-full)"
904
882
  echo -e "\t\t\t${GREEN}$(get_ai_model_for grouping)${ENDCOLOR} ${GRAY}(grouping)${ENDCOLOR} $(config_source_tag gitbasher.ai-model-grouping)"
905
883
  fi
884
+ }
885
+ function print_configuration {
886
+ echo -e "${YELLOW}Current configuration:${ENDCOLOR}"
887
+ local user_name=$(get_config_value user.name)
888
+ if [ -n "$user_name" ]; then
889
+ echo -e "\tuser.name:\t${YELLOW}$user_name${ENDCOLOR} $(config_source_tag user.name)"
890
+ else
891
+ echo -e "\tuser.name:\t${RED}not set${ENDCOLOR}"
892
+ fi
893
+ local user_email=$(get_config_value user.email)
894
+ if [ -n "$user_email" ]; then
895
+ echo -e "\tuser.email:\t${YELLOW}$user_email${ENDCOLOR} $(config_source_tag user.email)"
896
+ else
897
+ echo -e "\tuser.email:\t${RED}not set${ENDCOLOR}"
898
+ fi
899
+ echo -e "\tdefault:\t${YELLOW}$main_branch${ENDCOLOR} $(config_source_tag gitbasher.branch)"
900
+ echo -e "\tseparator:\t${YELLOW}$sep${ENDCOLOR} $(config_source_tag gitbasher.sep)"
901
+ echo -e "\teditor:\t\t${YELLOW}$editor${ENDCOLOR} $(config_source_tag core.editor)"
902
+ if [ "$ticket_name" != "" ]; then
903
+ echo -e "\tticket:\t\t${YELLOW}$ticket_name${ENDCOLOR} $(config_source_tag gitbasher.ticket)"
904
+ fi
905
+ if [ "$scopes" != "" ]; then
906
+ echo -e "\tscopes:\t\t${YELLOW}$scopes${ENDCOLOR} $(config_source_tag gitbasher.scopes)"
907
+ fi
908
+ print_ai_configuration
906
909
  local ai_proxy=$(get_ai_proxy)
907
910
  if [ -n "$ai_proxy" ]; then
908
911
  echo -e "\tAI proxy:\t${GREEN}$ai_proxy${ENDCOLOR} $(config_source_tag gitbasher.ai-proxy)"
@@ -1561,7 +1564,7 @@ function unset_config_value {
1561
1564
  if [ -n "$global_config" ]; then
1562
1565
  echo
1563
1566
  echo -e "${YELLOW}Global $1 is also configured: ${BLUE}$global_config${ENDCOLOR}"
1564
- echo -e "Do you want to clear it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
1567
+ echo -e "Do you want to clear it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
1565
1568
  local _unset_key=""
1566
1569
  read_key _unset_key || _unset_key="n"
1567
1570
  if is_yes "$_unset_key"; then
@@ -1826,8 +1829,8 @@ function is_valid_model_id {
1826
1829
  }
1827
1830
  function ai_provider_requires_api_key {
1828
1831
  case "$(get_ai_provider)" in
1829
- ollama) return 1 ;;
1830
- *) return 0 ;;
1832
+ ollama|claude) return 1 ;;
1833
+ *) return 0 ;;
1831
1834
  esac
1832
1835
  }
1833
1836
  function get_ai_model {
@@ -1836,18 +1839,22 @@ function get_ai_model {
1836
1839
  function set_ai_model {
1837
1840
  set_config_value gitbasher.ai-model "$1"
1838
1841
  }
1839
- readonly AI_DEFAULT_MODEL_SIMPLE="google/gemini-3.1-flash-lite-preview"
1840
- readonly AI_DEFAULT_MODEL_SUBJECT="google/gemini-3.1-flash-lite-preview"
1841
- readonly AI_DEFAULT_MODEL_FULL="google/gemini-3-flash-preview"
1842
- readonly AI_DEFAULT_MODEL_GROUPING="anthropic/claude-haiku-4.5"
1843
- readonly AI_DEFAULT_MODEL_SIMPLE_OPENAI="gpt-5.4-nano"
1844
- readonly AI_DEFAULT_MODEL_SUBJECT_OPENAI="gpt-5.4-nano"
1842
+ readonly AI_DEFAULT_MODEL_SIMPLE="google/gemini-3.1-flash-lite"
1843
+ readonly AI_DEFAULT_MODEL_SUBJECT="google/gemini-3.1-flash-lite"
1844
+ readonly AI_DEFAULT_MODEL_FULL="google/gemini-3.5-flash"
1845
+ readonly AI_DEFAULT_MODEL_GROUPING="google/gemini-3.5-flash"
1846
+ readonly AI_DEFAULT_MODEL_SIMPLE_OPENAI="gpt-5.4-mini"
1847
+ readonly AI_DEFAULT_MODEL_SUBJECT_OPENAI="gpt-5.4-mini"
1845
1848
  readonly AI_DEFAULT_MODEL_FULL_OPENAI="gpt-5.4-mini"
1846
1849
  readonly AI_DEFAULT_MODEL_GROUPING_OPENAI="gpt-5.4-mini"
1847
1850
  readonly AI_DEFAULT_MODEL_SIMPLE_OLLAMA="qwen3:8b"
1848
1851
  readonly AI_DEFAULT_MODEL_SUBJECT_OLLAMA="qwen3:8b"
1849
1852
  readonly AI_DEFAULT_MODEL_FULL_OLLAMA="qwen3:8b"
1850
1853
  readonly AI_DEFAULT_MODEL_GROUPING_OLLAMA="qwen3:8b"
1854
+ readonly AI_DEFAULT_MODEL_SIMPLE_CLAUDE="haiku"
1855
+ readonly AI_DEFAULT_MODEL_SUBJECT_CLAUDE="haiku"
1856
+ readonly AI_DEFAULT_MODEL_FULL_CLAUDE="sonnet"
1857
+ readonly AI_DEFAULT_MODEL_GROUPING_CLAUDE="sonnet"
1851
1858
  function get_ai_model_for {
1852
1859
  local task="$1"
1853
1860
  local task_model
@@ -1881,6 +1888,15 @@ function get_ai_model_for {
1881
1888
  *) echo "$AI_DEFAULT_MODEL_SIMPLE_OLLAMA" ;;
1882
1889
  esac
1883
1890
  ;;
1891
+ claude)
1892
+ case "$task" in
1893
+ simple) echo "$AI_DEFAULT_MODEL_SIMPLE_CLAUDE" ;;
1894
+ subject) echo "$AI_DEFAULT_MODEL_SUBJECT_CLAUDE" ;;
1895
+ full) echo "$AI_DEFAULT_MODEL_FULL_CLAUDE" ;;
1896
+ grouping) echo "$AI_DEFAULT_MODEL_GROUPING_CLAUDE" ;;
1897
+ *) echo "$AI_DEFAULT_MODEL_SIMPLE_CLAUDE" ;;
1898
+ esac
1899
+ ;;
1884
1900
  *)
1885
1901
  case "$task" in
1886
1902
  simple) echo "$AI_DEFAULT_MODEL_SIMPLE" ;;
@@ -2176,6 +2192,17 @@ function ai_smoke_check {
2176
2192
  echo -e "${GREEN}✓ Ollama reachable at ${base} (${count} model(s))${ENDCOLOR}"
2177
2193
  return 0
2178
2194
  fi
2195
+ if [ "$provider" = "claude" ]; then
2196
+ if ! command -v claude >/dev/null 2>&1; then
2197
+ echo -e "${RED}✗ claude CLI not found on PATH.${ENDCOLOR}" >&2
2198
+ echo -e "${CYAN}💡 Install it with ${BLUE}npm install -g @anthropic-ai/claude-code${ENDCOLOR}${CYAN}, then sign in once.${ENDCOLOR}" >&2
2199
+ return 1
2200
+ fi
2201
+ local claude_version
2202
+ claude_version=$(claude --version 2>/dev/null | head -1)
2203
+ echo -e "${GREEN}✓ claude CLI available${claude_version:+ (}${claude_version}${claude_version:+)}${ENDCOLOR}"
2204
+ return 0
2205
+ fi
2179
2206
  local api_key
2180
2207
  api_key=$(get_ai_api_key)
2181
2208
  if [ -z "$api_key" ]; then
@@ -2230,11 +2257,38 @@ function get_ai_timeout {
2230
2257
  printf '%s\n' "$t"
2231
2258
  return
2232
2259
  fi
2233
- if [ "$(get_ai_provider)" = "ollama" ]; then
2234
- printf '300\n'
2260
+ case "$(get_ai_provider)" in
2261
+ ollama|claude) printf '300\n' ;;
2262
+ *) printf '60\n' ;;
2263
+ esac
2264
+ }
2265
+ function _call_claude_cli {
2266
+ local system_prompt="$1"
2267
+ local user_prompt="$2"
2268
+ local model="$3"
2269
+ local timeout_s output rc
2270
+ timeout_s=$(get_ai_timeout)
2271
+ if command -v perl >/dev/null 2>&1; then
2272
+ output=$(printf '%s\n\n%s' "$system_prompt" "$user_prompt" \
2273
+ | perl -e 'alarm shift; exec @ARGV' "$timeout_s" \
2274
+ claude -p --model "$model" --output-format text 2>/dev/null)
2275
+ rc=$?
2235
2276
  else
2236
- printf '60\n'
2277
+ output=$(printf '%s\n\n%s' "$system_prompt" "$user_prompt" \
2278
+ | claude -p --model "$model" --output-format text 2>/dev/null)
2279
+ rc=$?
2237
2280
  fi
2281
+ if [ "$rc" -eq 142 ]; then
2282
+ echo -e "${RED}✗ claude timed out after ${timeout_s}s.${ENDCOLOR}" >&2
2283
+ echo -e "${YELLOW}Raise the limit with:${ENDCOLOR} ${GREEN}git config gitbasher.ai-timeout <seconds>${ENDCOLOR}" >&2
2284
+ return 1
2285
+ fi
2286
+ if [ "$rc" -ne 0 ] || [ -z "$output" ]; then
2287
+ echo -e "${RED}✗ claude CLI failed (exit ${rc}).${ENDCOLOR}" >&2
2288
+ echo -e "${CYAN}💡 Check it works: ${BLUE}echo hi | claude -p${ENDCOLOR}${CYAN} — you may need to sign in first.${ENDCOLOR}" >&2
2289
+ return 1
2290
+ fi
2291
+ printf '%s\n' "$output"
2238
2292
  }
2239
2293
  function call_ai_api {
2240
2294
  local system_prompt="$1"
@@ -2253,6 +2307,17 @@ function call_ai_api {
2253
2307
  clear_sensitive_vars
2254
2308
  return 1
2255
2309
  fi
2310
+ if [ "$provider" = "claude" ]; then
2311
+ local claude_out
2312
+ claude_out=$(_call_claude_cli "$system_prompt" "$user_prompt" "$model")
2313
+ if [ $? -ne 0 ] || [ -z "$claude_out" ]; then
2314
+ clear_sensitive_vars
2315
+ return 1
2316
+ fi
2317
+ clear_sensitive_vars
2318
+ echo "$claude_out"
2319
+ return 0
2320
+ fi
2256
2321
  local tokens_field="max_tokens"
2257
2322
  if [ "$provider" = "openai" ]; then
2258
2323
  tokens_field="max_completion_tokens"
@@ -2496,7 +2561,16 @@ function call_ai_api {
2496
2561
  echo "$ai_response"
2497
2562
  }
2498
2563
  function check_ai_available {
2499
- if ! command -v curl >/dev/null 2>&1; then
2564
+ local active_provider
2565
+ active_provider=$(get_ai_provider)
2566
+ if [ "$active_provider" = "claude" ]; then
2567
+ if ! command -v claude >/dev/null 2>&1; then
2568
+ echo -e "${RED}✗ claude CLI not found on PATH.${ENDCOLOR}" >&2
2569
+ echo -e "${YELLOW}Install it with:${ENDCOLOR} ${GREEN}npm install -g @anthropic-ai/claude-code${ENDCOLOR}" >&2
2570
+ echo -e "${YELLOW}Or switch provider with:${ENDCOLOR} ${GREEN}gitb cfg provider${ENDCOLOR}" >&2
2571
+ return 1
2572
+ fi
2573
+ elif ! command -v curl >/dev/null 2>&1; then
2500
2574
  echo -e "${RED}curl is required for AI functionality but not installed${ENDCOLOR}" >&2
2501
2575
  return 1
2502
2576
  fi
@@ -2732,7 +2806,7 @@ function set_default_branch {
2732
2806
  echo -e "${GREEN}✓ Set '${branch_name}' as the default gitbasher branch in '${project_name}'${ENDCOLOR}"
2733
2807
  echo
2734
2808
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
2735
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
2809
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
2736
2810
  yes_no_choice "\nSet '${branch_name}' globally" "true"
2737
2811
  main_branch=$(set_config_value gitbasher.branch $branch_name "true")
2738
2812
  }
@@ -2770,7 +2844,7 @@ function set_sep {
2770
2844
  echo -e "${GREEN}✓ Set '${sep}' as the branch name separator in '${project_name}'${ENDCOLOR}"
2771
2845
  echo
2772
2846
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
2773
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
2847
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
2774
2848
  yes_no_choice "\nSet '${sep}' globally" "true"
2775
2849
  sep=$(set_config_value gitbasher.sep "$new_sep" "true")
2776
2850
  }
@@ -2800,7 +2874,7 @@ function set_editor {
2800
2874
  echo -e "${GREEN}✓ Using editor '$editor' (${which_output})${ENDCOLOR}"
2801
2875
  echo
2802
2876
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
2803
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
2877
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
2804
2878
  yes_no_choice "\nSet '${editor}' globally" "true"
2805
2879
  editor=$(set_config_value core.editor "$choice" "true")
2806
2880
  }
@@ -2833,12 +2907,15 @@ function set_ticket {
2833
2907
  echo -e "${GREEN}✓ Set '${ticket_name}' as the ticket prefix in '${project_name}'${ENDCOLOR}"
2834
2908
  echo
2835
2909
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
2836
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
2910
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
2837
2911
  yes_no_choice "\nSet '${ticket_name}' globally" "true"
2838
2912
  ticket_name=$(set_config_value gitbasher.ticket "$ticket_name" "true")
2839
2913
  }
2840
2914
  function configure_ai_key {
2841
- if [ -z "$(git config --get gitbasher.ai-provider 2>/dev/null)" ] \
2915
+ local _chained=""
2916
+ [ "$1" = "--chained" ] && _chained="true"
2917
+ if [ -z "$_chained" ] \
2918
+ && [ -z "$(git config --get gitbasher.ai-provider 2>/dev/null)" ] \
2842
2919
  && [ -z "$(git config --global --get gitbasher.ai-provider 2>/dev/null)" ]; then
2843
2920
  configure_ai_provider
2844
2921
  return
@@ -2848,9 +2925,10 @@ function configure_ai_key {
2848
2925
  provider_upper=$(echo "$provider" | tr '[:lower:]' '[:upper:]')
2849
2926
  echo -e "${YELLOW}Enter AI API key for provider '${provider}'${ENDCOLOR}"
2850
2927
  echo
2851
- if [ "$provider" = "ollama" ]; then
2928
+ if ! ai_provider_requires_api_key; then
2852
2929
  echo -e "${YELLOW}Provider '${provider}' does not require an API key.${ENDCOLOR}"
2853
2930
  echo -e "Switch providers with ${GREEN}gitb cfg provider${ENDCOLOR} to set one."
2931
+ if [ -n "$_chained" ]; then return 0; fi
2854
2932
  exit
2855
2933
  fi
2856
2934
  ai_api_key=$(get_ai_api_key)
@@ -2858,6 +2936,24 @@ function configure_ai_key {
2858
2936
  echo -e "${YELLOW}No AI API key is set for '${provider}'.${ENDCOLOR}"
2859
2937
  else
2860
2938
  echo -e "AI API key for '${provider}' is ${GREEN}configured${ENDCOLOR}: ${BLUE}$(mask_api_key "$ai_api_key")${ENDCOLOR}"
2939
+ echo
2940
+ echo -e "Press ${YELLOW}Enter${ENDCOLOR} to keep it, ${YELLOW}r${ENDCOLOR} to replace, or ${YELLOW}0${ENDCOLOR} to remove"
2941
+ local _key_action=""
2942
+ read_key _key_action || { if [ -n "$_chained" ]; then return 130; fi; exit; }
2943
+ if [ "$_key_action" = "0" ]; then
2944
+ unset_ai_api_key
2945
+ echo
2946
+ echo -e "${GREEN}✓ Removed AI API key for '${provider}' from '${project_name}'${ENDCOLOR}"
2947
+ if [ -n "$_chained" ]; then return 0; fi
2948
+ exit
2949
+ fi
2950
+ normalize_key "$_key_action"
2951
+ if [ "$normalized_key" != "r" ]; then
2952
+ echo -e "Keeping the existing key."
2953
+ if [ -n "$_chained" ]; then return 0; fi
2954
+ exit
2955
+ fi
2956
+ echo
2861
2957
  fi
2862
2958
  case "$provider" in
2863
2959
  openai)
@@ -2882,7 +2978,8 @@ function configure_ai_key {
2882
2978
  echo -e " ${GREEN}env var${ENDCOLOR} — recommended; the key never touches disk via gitbasher"
2883
2979
  echo -e " ${BLUE}git config${ENDCOLOR} — convenient, but stored in plaintext under .git/config (or ~/.gitconfig if global)"
2884
2980
  echo
2885
- read -n 1 -p "Use environment variable (recommended)? (y/n) " ai_storage_choice || ai_storage_choice="n"
2981
+ read -n 1 -p "Use environment variable (recommended)? (Y/n) " ai_storage_choice \
2982
+ || { if [ -n "$_chained" ]; then echo; return 130; fi; ai_storage_choice="n"; }
2886
2983
  echo
2887
2984
  if is_yes "$ai_storage_choice"; then
2888
2985
  echo
@@ -2892,6 +2989,7 @@ function configure_ai_key {
2892
2989
  echo -e "${CYAN}gitbasher checks env vars before git config every run, so this takes precedence.${ENDCOLOR}"
2893
2990
  echo -e "Append it now with:"
2894
2991
  echo -e " ${GREEN}echo \"export GITB_AI_API_KEY_${provider_upper}='your-${provider}-key'\" >> $(printf '%q' "$rc_file")${ENDCOLOR}"
2992
+ if [ -n "$_chained" ]; then return 0; fi
2895
2993
  exit
2896
2994
  fi
2897
2995
  echo
@@ -2901,20 +2999,24 @@ function configure_ai_key {
2901
2999
  echo -e "${YELLOW}Input is hidden — type the key and press Enter.${ENDCOLOR}"
2902
3000
  read_silent_input ai_key_input "API Key: "
2903
3001
  if [ "$ai_key_input" == "" ]; then
3002
+ if [ -n "$_chained" ]; then return 0; fi
2904
3003
  exit
2905
3004
  fi
2906
3005
  if [ "$ai_key_input" == "0" ]; then
2907
3006
  unset_ai_api_key
2908
3007
  echo
2909
3008
  echo -e "${GREEN}✓ Removed AI API key for '${provider}' from '${project_name}'${ENDCOLOR}"
3009
+ if [ -n "$_chained" ]; then return 0; fi
2910
3010
  exit
2911
3011
  fi
2912
3012
  echo
2913
3013
  if [[ ! "$ai_key_input" =~ ^[a-zA-Z0-9._-]{20,}$ ]]; then
2914
3014
  echo -e "${YELLOW}⚠ API key format does not look like a valid ${provider} key.${ENDCOLOR}" >&2
2915
- read -n 1 -p "Continue anyway? (y/n) " -s choice || choice="n"
3015
+ read -n 1 -p "Continue anyway? (Y/n) " -s choice \
3016
+ || { if [ -n "$_chained" ]; then echo; return 130; fi; choice="n"; }
2916
3017
  echo
2917
3018
  if ! is_yes "$choice"; then
3019
+ if [ -n "$_chained" ]; then return 1; fi
2918
3020
  exit 1
2919
3021
  fi
2920
3022
  fi
@@ -2923,14 +3025,24 @@ function configure_ai_key {
2923
3025
  echo
2924
3026
  ai_smoke_check || true
2925
3027
  echo
2926
- [ "$GITBASHER_NO_REPO" = "true" ] && exit
2927
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
3028
+ if [ "$GITBASHER_NO_REPO" = "true" ]; then
3029
+ if [ -n "$_chained" ]; then return 0; fi
3030
+ exit
3031
+ fi
3032
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
2928
3033
  echo -e "${RED}⚠ Global API keys are stored in plaintext in ~/.gitconfig.${ENDCOLOR}"
2929
3034
  echo -e "${CYAN}💡 For better security, set ${BLUE}GITB_AI_API_KEY_${provider_upper}${CYAN} as an environment variable instead.${ENDCOLOR}"
2930
- yes_no_choice "\nSet AI API key globally" "true"
2931
- ai_api_key=$(set_ai_api_key "$ai_key_input" "true")
3035
+ local _key_global=""
3036
+ read_key _key_global || { if [ -n "$_chained" ]; then return 130; fi; _key_global="n"; }
3037
+ if is_yes "$_key_global"; then
3038
+ echo -e "${YELLOW}Set AI API key globally${ENDCOLOR}"
3039
+ ai_api_key=$(set_ai_api_key "$ai_key_input" "true")
3040
+ fi
3041
+ return 0
2932
3042
  }
2933
3043
  function configure_ai_provider {
3044
+ local _chained=""
3045
+ [ "$1" = "--chained" ] && _chained="true"
2934
3046
  echo -e "${YELLOW}Choose AI Provider${ENDCOLOR}"
2935
3047
  echo
2936
3048
  local current_provider=$(get_ai_provider)
@@ -2944,10 +3056,15 @@ function configure_ai_provider {
2944
3056
  echo -e " 1. ${BLUE}openrouter${ENDCOLOR} Aggregator with hundreds of models — needs an API key"
2945
3057
  echo -e " 2. ${BLUE}openai${ENDCOLOR} OpenAI direct (GPT-5 family) — needs an API key"
2946
3058
  echo -e " 3. ${BLUE}ollama${ENDCOLOR} Local or remote models (you'll set the host) — no API key required"
3059
+ echo -e " 4. ${BLUE}claude${ENDCOLOR} Local Claude Code CLI (claude -p) — uses your Claude account, no API key"
2947
3060
  echo
2948
3061
  echo -e "Press Enter to keep the current provider, or enter 0 to reset to default (${AI_DEFAULT_PROVIDER})"
2949
- read_editable_input choice "Choice (1-3): "
3062
+ if ! read_editable_input choice "Choice (1-4): "; then
3063
+ if [ -n "$_chained" ]; then return 130; fi
3064
+ exit
3065
+ fi
2950
3066
  if [ "$choice" == "" ]; then
3067
+ if [ -n "$_chained" ]; then return 0; fi
2951
3068
  exit
2952
3069
  fi
2953
3070
  if [ "$choice" == "0" ]; then
@@ -2955,6 +3072,7 @@ function configure_ai_provider {
2955
3072
  git config --global --unset gitbasher.ai-provider 2>/dev/null
2956
3073
  echo
2957
3074
  echo -e "${GREEN}✓ Reset provider to default (${AI_DEFAULT_PROVIDER})${ENDCOLOR}"
3075
+ if [ -n "$_chained" ]; then return 0; fi
2958
3076
  exit
2959
3077
  fi
2960
3078
  local new_provider
@@ -2963,8 +3081,10 @@ function configure_ai_provider {
2963
3081
  1) new_provider="openrouter" ;;
2964
3082
  2) new_provider="openai" ;;
2965
3083
  3) new_provider="ollama" ;;
3084
+ 4) new_provider="claude" ;;
2966
3085
  *)
2967
3086
  echo -e "${RED}✗ Invalid choice.${ENDCOLOR}" >&2
3087
+ if [ -n "$_chained" ]; then return 1; fi
2968
3088
  exit 1
2969
3089
  ;;
2970
3090
  esac
@@ -2987,6 +3107,7 @@ function configure_ai_provider {
2987
3107
  host_input="${host_input%/}"
2988
3108
  if ! validate_proxy_url "$host_input"; then
2989
3109
  echo -e "${RED}✗ '$host_input' does not look like a valid host URL (expected http://host[:port]).${ENDCOLOR}"
3110
+ if [ -n "$_chained" ]; then return 1; fi
2990
3111
  exit 1
2991
3112
  fi
2992
3113
  set_ai_ollama_host "$host_input" >/dev/null
@@ -2998,6 +3119,16 @@ function configure_ai_provider {
2998
3119
  echo
2999
3120
  ai_smoke_check || true
3000
3121
  ;;
3122
+ claude)
3123
+ echo
3124
+ if command -v claude >/dev/null 2>&1; then
3125
+ ai_smoke_check || true
3126
+ else
3127
+ echo -e "${RED}✗ claude CLI not found on PATH.${ENDCOLOR}"
3128
+ echo -e "${CYAN}💡 Install it with ${BLUE}npm install -g @anthropic-ai/claude-code${CYAN}, then sign in once.${ENDCOLOR}"
3129
+ fi
3130
+ echo -e "${CYAN}💡 Pick a model with ${GREEN}gitb cfg model${CYAN} (haiku, sonnet, opus).${ENDCOLOR}"
3131
+ ;;
3001
3132
  *)
3002
3133
  if [ -n "$(get_ai_api_key)" ]; then
3003
3134
  echo
@@ -3007,9 +3138,9 @@ function configure_ai_provider {
3007
3138
  esac
3008
3139
  echo
3009
3140
  if [ "$GITBASHER_NO_REPO" != "true" ]; then
3010
- echo -e "Do you want to set this provider ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
3141
+ echo -e "Do you want to set this provider ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
3011
3142
  local _prov_key=""
3012
- read_key _prov_key || _prov_key="n"
3143
+ read_key _prov_key || { if [ -n "$_chained" ]; then return 130; fi; _prov_key="n"; }
3013
3144
  if is_yes "$_prov_key"; then
3014
3145
  echo -e "${YELLOW}Set AI provider globally${ENDCOLOR}"
3015
3146
  set_config_value gitbasher.ai-provider "$new_provider" "true"
@@ -3018,12 +3149,12 @@ function configure_ai_provider {
3018
3149
  fi
3019
3150
  fi
3020
3151
  fi
3021
- if ai_provider_requires_api_key && [ -z "$(get_ai_api_key)" ]; then
3152
+ if [ -z "$_chained" ] && ai_provider_requires_api_key && [ -z "$(get_ai_api_key)" ]; then
3022
3153
  echo
3023
3154
  echo -e "${YELLOW}No API key is set for '${new_provider}'.${ENDCOLOR}"
3024
3155
  echo -e "${CYAN}AI commands will be blocked until a key is configured.${ENDCOLOR}"
3025
3156
  echo
3026
- echo -e "Set one now? (y/n)"
3157
+ echo -e "Set one now? (Y/n)"
3027
3158
  if read -n 1 -s key_choice && is_yes "$key_choice"; then
3028
3159
  echo
3029
3160
  configure_ai_key
@@ -3034,6 +3165,8 @@ function configure_ai_provider {
3034
3165
  fi
3035
3166
  }
3036
3167
  function configure_ai_model {
3168
+ local _chained=""
3169
+ [ "$1" = "--chained" ] && _chained="true"
3037
3170
  echo -e "${YELLOW}Configure AI Model${ENDCOLOR}"
3038
3171
  echo
3039
3172
  current_model=$(get_ai_model)
@@ -3054,9 +3187,9 @@ function configure_ai_model {
3054
3187
  echo -e "Popular models for provider '${GREEN}$(get_ai_provider)${ENDCOLOR}':"
3055
3188
  case "$(get_ai_provider)" in
3056
3189
  openai)
3057
- echo -e " • ${BLUE}gpt-5.4-nano${ENDCOLOR} - Cheapest+fastest, ideal for one-line subjects (~\$0.20/\$1.25 per M)"
3058
- echo -e " • ${BLUE}gpt-5.4-mini${ENDCOLOR} - Balanced for full-body prose and strict TSV output (~\$0.75/\$4.50 per M)"
3059
- echo -e " • ${BLUE}gpt-5.4${ENDCOLOR} - Flagship, only worth it for the hardest grouping cases"
3190
+ echo -e " • ${BLUE}gpt-5.4-mini${ENDCOLOR} - Default: as fast as nano with better quality (~\$0.75/\$4.50 per M)"
3191
+ echo -e " • ${BLUE}gpt-5.4-nano${ENDCOLOR} - Budget option for high volume (~\$0.20/\$1.25 per M)"
3192
+ echo -e " • ${BLUE}gpt-5.5${ENDCOLOR} - Flagship, only worth it for the hardest grouping cases"
3060
3193
  ;;
3061
3194
  ollama)
3062
3195
  echo -e " • ${BLUE}qwen3:8b${ENDCOLOR} - Best small instruction-follower; most stable structured output"
@@ -3064,12 +3197,18 @@ function configure_ai_model {
3064
3197
  echo -e " • ${BLUE}qwen2.5-coder:7b${ENDCOLOR} - Code-focused; good when most diffs are code"
3065
3198
  echo -e " Run ${BLUE}ollama list${ENDCOLOR} to see what you have pulled locally."
3066
3199
  ;;
3200
+ claude)
3201
+ echo -e " • ${BLUE}haiku${ENDCOLOR} - Fastest and cheapest; ideal for one-line messages"
3202
+ echo -e " • ${BLUE}sonnet${ENDCOLOR} - Best balance for full-body prose and grouping"
3203
+ echo -e " • ${BLUE}opus${ENDCOLOR} - Strongest, for the hardest cases"
3204
+ echo -e " Aliases resolve to the current model generation; full ids (${BLUE}claude-haiku-4-5${ENDCOLOR}) also work."
3205
+ ;;
3067
3206
  *)
3068
3207
  echo -e " • ${BLUE}openrouter/auto${ENDCOLOR} - Auto-select best available model"
3069
- echo -e " • ${BLUE}google/gemini-3.1-flash-lite-preview${ENDCOLOR} - Fastest, cheapest"
3070
- echo -e " • ${BLUE}google/gemini-3-flash-preview${ENDCOLOR} - Better body prose"
3208
+ echo -e " • ${BLUE}google/gemini-3.1-flash-lite${ENDCOLOR} - Fastest, cheapest (~\$0.25/\$1.50 per M)"
3209
+ echo -e " • ${BLUE}google/gemini-3.5-flash${ENDCOLOR} - Current flash generation, better body prose"
3071
3210
  echo -e " • ${BLUE}anthropic/claude-haiku-4.5${ENDCOLOR} - Strict instruction following"
3072
- echo -e " • ${BLUE}openai/gpt-5.4-mini${ENDCOLOR} - Strong reasoning, low cost"
3211
+ echo -e " • ${BLUE}anthropic/claude-sonnet-5${ENDCOLOR} - Premium quality for the hardest cases"
3073
3212
  ;;
3074
3213
  esac
3075
3214
  echo
@@ -3098,8 +3237,12 @@ function configure_ai_model {
3098
3237
  echo
3099
3238
  fi
3100
3239
  echo -e "Press Enter to exit without changes, or 0 to clear the override (use per-task defaults)"
3101
- read_editable_input model_input "Model ID: "
3240
+ if ! read_editable_input model_input "Model ID: "; then
3241
+ if [ -n "$_chained" ]; then return 130; fi
3242
+ exit
3243
+ fi
3102
3244
  if [ "$model_input" == "" ]; then
3245
+ if [ -n "$_chained" ]; then return 0; fi
3103
3246
  exit
3104
3247
  fi
3105
3248
  if [ "$model_input" == "0" ]; then
@@ -3107,6 +3250,7 @@ function configure_ai_model {
3107
3250
  git config --global --unset gitbasher.ai-model 2>/dev/null
3108
3251
  echo
3109
3252
  echo -e "${GREEN}✓ Cleared global model override — each task now uses its per-task default${ENDCOLOR}"
3253
+ if [ -n "$_chained" ]; then return 0; fi
3110
3254
  exit
3111
3255
  fi
3112
3256
  echo
@@ -3115,23 +3259,61 @@ function configure_ai_model {
3115
3259
  model_input="${ollama_model_list[$((model_input - 1))]}"
3116
3260
  else
3117
3261
  echo -e "${RED}✗ Invalid selection: ${model_input}${ENDCOLOR}" >&2
3262
+ if [ -n "$_chained" ]; then return 1; fi
3118
3263
  exit 1
3119
3264
  fi
3120
3265
  fi
3121
3266
  if ! is_valid_model_id "$model_input"; then
3122
3267
  echo -e "${RED}✗ Invalid model ID format.${ENDCOLOR}" >&2
3123
3268
  echo -e "${YELLOW}Use only letters, numbers, dots, dashes, underscores, colons, and slashes.${ENDCOLOR}" >&2
3269
+ if [ -n "$_chained" ]; then return 1; fi
3124
3270
  exit 1
3125
3271
  fi
3126
3272
  set_ai_model "$model_input"
3127
3273
  echo -e "${GREEN}✓ Set AI model to '$model_input' for '${project_name}'${ENDCOLOR}"
3128
3274
  echo
3129
- [ "$GITBASHER_NO_REPO" = "true" ] && exit
3130
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
3131
- yes_no_choice "\nSet AI model globally" "true"
3132
- set_config_value gitbasher.ai-model "$model_input" "true"
3275
+ if [ "$GITBASHER_NO_REPO" = "true" ]; then
3276
+ if [ -n "$_chained" ]; then return 0; fi
3277
+ exit
3278
+ fi
3279
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
3280
+ local _model_global=""
3281
+ read_key _model_global || { if [ -n "$_chained" ]; then return 130; fi; _model_global="n"; }
3282
+ if is_yes "$_model_global"; then
3283
+ echo -e "${YELLOW}Set AI model globally${ENDCOLOR}"
3284
+ set_config_value gitbasher.ai-model "$model_input" "true"
3285
+ fi
3286
+ return 0
3133
3287
  }
3134
- function configure_ai_proxy {
3288
+ function configure_ai_wizard {
3289
+ local _rc
3290
+ echo -e "${YELLOW}Step 1/3 — Provider${ENDCOLOR}"
3291
+ echo
3292
+ configure_ai_provider --chained
3293
+ _rc=$?
3294
+ if [ "$_rc" -eq 130 ]; then prompt_aborted; fi
3295
+ echo
3296
+ echo -e "${YELLOW}Step 2/3 — API key${ENDCOLOR}"
3297
+ echo
3298
+ if ai_provider_requires_api_key; then
3299
+ configure_ai_key --chained
3300
+ _rc=$?
3301
+ if [ "$_rc" -eq 130 ]; then prompt_aborted; fi
3302
+ else
3303
+ echo -e "Provider '$(get_ai_provider)' does not require an API key — skipping."
3304
+ fi
3305
+ echo
3306
+ echo -e "${YELLOW}Step 3/3 — Model${ENDCOLOR}"
3307
+ echo
3308
+ configure_ai_model --chained
3309
+ _rc=$?
3310
+ if [ "$_rc" -eq 130 ]; then prompt_aborted; fi
3311
+ echo
3312
+ echo -e "${GREEN}✓ AI setup complete${ENDCOLOR}"
3313
+ echo
3314
+ print_ai_configuration
3315
+ }
3316
+ function configure_ai_proxy {
3135
3317
  echo -e "${YELLOW}Configure AI HTTP/SOCKS Proxy${ENDCOLOR}"
3136
3318
  echo
3137
3319
  ai_proxy=$(get_ai_proxy)
@@ -3175,7 +3357,7 @@ function configure_ai_proxy {
3175
3357
  echo -e " ${BLUE}gitb commit aif${ENDCOLOR} - Fast AI commit through proxy"
3176
3358
  echo
3177
3359
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
3178
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
3360
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
3179
3361
  yes_no_choice "\nSet AI proxy globally" "true"
3180
3362
  set_ai_proxy "$ai_proxy_input"
3181
3363
  git config --global gitbasher.ai-proxy "$ai_proxy_input"
@@ -3209,7 +3391,7 @@ function configure_ai_history {
3209
3391
  if [ "$limit_input" -gt 20 ]; then
3210
3392
  echo -e "${YELLOW}High values may exceed token limits and slow down AI responses.${ENDCOLOR}"
3211
3393
  fi
3212
- read -n 1 -p "Continue anyway? (y/n) " -s choice || choice="n"
3394
+ read -n 1 -p "Continue anyway? (Y/n) " -s choice || choice="n"
3213
3395
  echo
3214
3396
  if ! is_yes "$choice"; then
3215
3397
  exit
@@ -3219,7 +3401,7 @@ function configure_ai_history {
3219
3401
  echo -e "${GREEN}✓ Set AI commit history limit to ${limit_input} for '${project_name}'${ENDCOLOR}"
3220
3402
  echo
3221
3403
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
3222
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
3404
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
3223
3405
  yes_no_choice "\nSet AI commit history limit globally" "true"
3224
3406
  git config --global gitbasher.ai-commit-history-limit "$limit_input"
3225
3407
  }
@@ -3257,7 +3439,7 @@ function configure_push_warn_size {
3257
3439
  fi
3258
3440
  echo
3259
3441
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
3260
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
3442
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
3261
3443
  yes_no_choice "\nSet push size warning globally" "true"
3262
3444
  git config --global gitbasher.push-warn-size "$limit_input"
3263
3445
  }
@@ -3286,7 +3468,7 @@ function configure_ai_diff {
3286
3468
  lines_input="$validated_number"
3287
3469
  if [ "$lines_input" -lt 50 ] || [ "$lines_input" -gt 2000 ]; then
3288
3470
  echo -e "${YELLOW}⚠ Value is outside the recommended range (50-2000).${ENDCOLOR}"
3289
- read -n 1 -p "Continue anyway? (y/n) " -s choice || choice="n"
3471
+ read -n 1 -p "Continue anyway? (Y/n) " -s choice || choice="n"
3290
3472
  echo
3291
3473
  if ! is_yes "$choice"; then
3292
3474
  exit
@@ -3304,7 +3486,7 @@ function configure_ai_diff {
3304
3486
  chars_input="$validated_number"
3305
3487
  if [ "$chars_input" -lt 4000 ] || [ "$chars_input" -gt 100000 ]; then
3306
3488
  echo -e "${YELLOW}⚠ Value is outside the recommended range (4000-100000).${ENDCOLOR}"
3307
- read -n 1 -p "Continue anyway? (y/n) " -s choice || choice="n"
3489
+ read -n 1 -p "Continue anyway? (Y/n) " -s choice || choice="n"
3308
3490
  echo
3309
3491
  if ! is_yes "$choice"; then
3310
3492
  exit
@@ -3319,7 +3501,7 @@ function configure_ai_diff {
3319
3501
  fi
3320
3502
  echo
3321
3503
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
3322
- echo -e "Do you want to apply ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
3504
+ echo -e "Do you want to apply ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
3323
3505
  yes_no_choice "\nApply AI diff settings globally" "true"
3324
3506
  if [ -n "$lines_input" ]; then
3325
3507
  git config --global gitbasher.ai-diff-limit "$lines_input"
@@ -3367,7 +3549,7 @@ function set_scopes {
3367
3549
  echo -e "${GREEN}✓ Set '${scopes}' as the scopes list in '${project_name}'${ENDCOLOR}"
3368
3550
  echo
3369
3551
  [ "$GITBASHER_NO_REPO" = "true" ] && exit
3370
- echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
3552
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
3371
3553
  yes_no_choice "\nSet '${scopes}' globally" "true"
3372
3554
  git config --global --replace-all gitbasher.scopes "$scopes_raw"
3373
3555
  }
@@ -3511,7 +3693,8 @@ function config_script {
3511
3693
  editor|ed|e) set_editor_cfg="true";;
3512
3694
  ticket|jira|ti|t) set_ticket_cfg="true";;
3513
3695
  scopes|scope|sc|s) set_scopes_cfg="true";;
3514
- ai|llm|key) set_ai_cfg="true";;
3696
+ ai|llm) set_ai_cfg="true";;
3697
+ key) set_ai_key_cfg="true";;
3515
3698
  provider|prov) set_ai_provider_cfg="true";;
3516
3699
  model|m) set_ai_model_cfg="true";;
3517
3700
  proxy|prx|p) set_proxy_cfg="true";;
@@ -3536,6 +3719,8 @@ function config_script {
3536
3719
  elif [ -n "${set_scopes_cfg}" ]; then
3537
3720
  header="$header SCOPES LIST"
3538
3721
  elif [ -n "${set_ai_cfg}" ]; then
3722
+ header="$header AI SETUP"
3723
+ elif [ -n "${set_ai_key_cfg}" ]; then
3539
3724
  header="$header AI API KEY"
3540
3725
  elif [ -n "${set_ai_provider_cfg}" ]; then
3541
3726
  header="$header AI PROVIDER"
@@ -3591,6 +3776,10 @@ function config_script {
3591
3776
  exit
3592
3777
  fi
3593
3778
  if [ "$set_ai_cfg" == "true" ]; then
3779
+ configure_ai_wizard
3780
+ exit
3781
+ fi
3782
+ if [ "$set_ai_key_cfg" == "true" ]; then
3594
3783
  configure_ai_key
3595
3784
  exit
3596
3785
  fi
@@ -3638,8 +3827,9 @@ function config_script {
3638
3827
  print_help_row $PAD "editor" "ed, e" "Set the editor for commit messages"
3639
3828
  print_help_row $PAD "ticket" "ti, t, jira" "Set the ticket prefix used in commits and branches"
3640
3829
  print_help_row $PAD "scopes" "sc" "Set the list of suggested commit scopes"
3641
- print_help_row $PAD "ai" "llm, key" "Set the AI API key"
3642
- print_help_row $PAD "provider" "prov" "Choose the AI provider (openrouter, openai, ollama)"
3830
+ print_help_row $PAD "ai" "llm" "Set up AI: provider, API key, and model (wizard)"
3831
+ print_help_row $PAD "key" "" "Set the AI API key only"
3832
+ print_help_row $PAD "provider" "prov" "Choose the AI provider (openrouter, openai, ollama, claude)"
3643
3833
  print_help_row $PAD "model" "m" "Set the AI model override"
3644
3834
  print_help_row $PAD "proxy" "prx, p" "Set an HTTP/SOCKS proxy for AI requests"
3645
3835
  print_help_row $PAD "history" "hist" "Set how many recent commits to include in AI prompts"
@@ -3653,7 +3843,7 @@ function config_script {
3653
3843
  echo -e " ${GREEN}gitb cfg${ENDCOLOR} Show current configuration"
3654
3844
  echo -e " ${GREEN}gitb cfg user${ENDCOLOR} Set user.name and user.email"
3655
3845
  echo -e " ${GREEN}gitb cfg default${ENDCOLOR} Choose the default gitbasher branch"
3656
- echo -e " ${GREEN}gitb cfg ai${ENDCOLOR} Set the AI API key (per-repo or globally)"
3846
+ echo -e " ${GREEN}gitb cfg ai${ENDCOLOR} Interactive AI setup (provider, key, model)"
3657
3847
  exit
3658
3848
  fi
3659
3849
  print_configuration
@@ -3718,7 +3908,7 @@ function merge_script {
3718
3908
  elif [ "$current_local_commit" != "$current_remote_commit" ]; then
3719
3909
  echo -e "${YELLOW}⚠ Remote changes detected on ${current_branch}.${ENDCOLOR}"
3720
3910
  echo
3721
- echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} first (y/n)?"
3911
+ echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} first (Y/n)?"
3722
3912
  read -n 1 -s choice || prompt_aborted
3723
3913
  if is_yes "$choice"; then
3724
3914
  echo
@@ -3772,7 +3962,7 @@ function merge_script {
3772
3962
  elif [ "$local_commit" != "$remote_commit" ]; then
3773
3963
  echo -e "${YELLOW}⚠ Remote changes detected.${ENDCOLOR}"
3774
3964
  echo
3775
- echo -e "Do you want to fetch ${YELLOW}${origin_name}/${merge_branch}${ENDCOLOR} before merge (y/n)?"
3965
+ echo -e "Do you want to fetch ${YELLOW}${origin_name}/${merge_branch}${ENDCOLOR} before merge (Y/n)?"
3776
3966
  read -n 1 -s choice || prompt_aborted
3777
3967
  if is_yes "$choice"; then
3778
3968
  echo
@@ -3901,7 +4091,7 @@ function resolve_conflicts {
3901
4091
  if [ "$choice" == "4" ]; then
3902
4092
  echo
3903
4093
  echo -e "${RED}⚠ This will discard your current changes for these files.${ENDCOLOR}"
3904
- echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/n)?"
4094
+ echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/N)?"
3905
4095
  if confirm_destructive; then
3906
4096
  echo -e "${YELLOW}Accepting all incoming changes...${ENDCOLOR}"
3907
4097
  deleted_files=$(git --no-pager diff --name-only --diff-filter=D --relative 2>/dev/null)
@@ -3909,7 +4099,7 @@ function resolve_conflicts {
3909
4099
  echo -e "${YELLOW}⚠ Some files were deleted in the incoming branch:${ENDCOLOR}"
3910
4100
  echo "$deleted_files" | sed 's/^/\t/'
3911
4101
  echo
3912
- echo -e "Do you want to continue and accept the deletions (y/n)?"
4102
+ echo -e "Do you want to continue and accept the deletions (y/N)?"
3913
4103
  if ! confirm_destructive; then
3914
4104
  echo -e "${YELLOW}Cancelled. Continuing...${ENDCOLOR}"
3915
4105
  continue
@@ -3967,7 +4157,7 @@ function resolve_conflicts {
3967
4157
  if [ "$choice" == "5" ]; then
3968
4158
  echo
3969
4159
  echo -e "${RED}⚠ This will discard the incoming changes for these files.${ENDCOLOR}"
3970
- echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/n)?"
4160
+ echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/N)?"
3971
4161
  if confirm_destructive; then
3972
4162
  echo -e "${YELLOW}Accepting all current changes...${ENDCOLOR}"
3973
4163
  deleted_files=$(git --no-pager diff --name-only --diff-filter=D --relative 2>/dev/null)
@@ -3975,7 +4165,7 @@ function resolve_conflicts {
3975
4165
  echo -e "${YELLOW}⚠ Some files were deleted in the current branch:${ENDCOLOR}"
3976
4166
  echo "$deleted_files" | sed 's/^/\t/'
3977
4167
  echo
3978
- echo -e "Do you want to continue and accept the deletions (y/n)?"
4168
+ echo -e "Do you want to continue and accept the deletions (y/N)?"
3979
4169
  if ! confirm_destructive; then
3980
4170
  echo -e "${YELLOW}Cancelled. Continuing...${ENDCOLOR}"
3981
4171
  continue
@@ -4094,7 +4284,7 @@ ${staged_with_tab}
4094
4284
  echo
4095
4285
  echo -e "${YELLOW}⚠ Merge commit message cannot be empty.${ENDCOLOR}"
4096
4286
  echo
4097
- read -n 1 -p "Do you want to try for one more time? (y/n) " -s -e choice || choice="n"
4287
+ read -n 1 -p "Do you want to try for one more time? (Y/n) " -s -e choice || choice="n"
4098
4288
  if ! is_yes "$choice"; then
4099
4289
  while IFS= read -r _cf; do
4100
4290
  [ -n "$_cf" ] && git restore --staged -- ":(literal)$_cf" 2>/dev/null
@@ -4181,7 +4371,7 @@ function rebase_script {
4181
4371
  elif [ "$current_local_commit" != "$current_remote_commit" ]; then
4182
4372
  echo -e "${YELLOW}⚠ Remote changes detected on ${current_branch}.${ENDCOLOR}"
4183
4373
  echo
4184
- echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} first (y/n)?"
4374
+ echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} first (Y/n)?"
4185
4375
  read -n 1 -s choice || prompt_aborted
4186
4376
  if is_yes "$choice"; then
4187
4377
  echo
@@ -4340,7 +4530,7 @@ function rebase_branch {
4340
4530
  fixup_commits_with_dates=$(git log --format="${YELLOW}%h${ENDCOLOR} (${BLUE}%cr${ENDCOLOR}) ${BOLD}%s${ENDCOLOR}" --grep="^fixup!" $ref..HEAD 2>/dev/null || echo "")
4341
4531
  echo -e "$fixup_commits_with_dates" | sed 's/^/\t/'
4342
4532
  echo
4343
- echo -e "Proceed with fast autosquash rebase (y/n)?"
4533
+ echo -e "Proceed with fast autosquash rebase (y/N)?"
4344
4534
  if ! confirm_destructive; then
4345
4535
  echo -e "${YELLOW}Rebase cancelled${ENDCOLOR}"
4346
4536
  exit
@@ -4526,7 +4716,7 @@ function rebase_conflicts {
4526
4716
  if [ "$force_skip" != "true" ]; then
4527
4717
  echo
4528
4718
  echo -e "${RED}⚠ Dropping this commit will permanently remove it from history.${ENDCOLOR}"
4529
- echo -e "Are you sure you want to ${RED}skip and discard${ENDCOLOR} this commit (y/n)?"
4719
+ echo -e "Are you sure you want to ${RED}skip and discard${ENDCOLOR} this commit (y/N)?"
4530
4720
  if ! confirm_destructive; then
4531
4721
  echo -e "${YELLOW}Continuing...${ENDCOLOR}"
4532
4722
  continue
@@ -4545,7 +4735,7 @@ function rebase_conflicts {
4545
4735
  fi
4546
4736
  if [ "$choice" == "4" ]; then
4547
4737
  echo
4548
- echo -e "Are you sure you want to ${YELLOW}abort rebase${ENDCOLOR} (y/n)?"
4738
+ echo -e "Are you sure you want to ${YELLOW}abort rebase${ENDCOLOR} (y/N)?"
4549
4739
  if confirm_destructive; then
4550
4740
  echo -e "${YELLOW}Aborting rebase...${ENDCOLOR}"
4551
4741
  git rebase --abort
@@ -4558,7 +4748,7 @@ function rebase_conflicts {
4558
4748
  if [ "$choice" == "5" ]; then
4559
4749
  echo
4560
4750
  echo -e "${RED}⚠ This will discard your current changes for these files.${ENDCOLOR}"
4561
- echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/n)?"
4751
+ echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/N)?"
4562
4752
  if confirm_destructive; then
4563
4753
  echo -e "${YELLOW}Accepting all incoming changes...${ENDCOLOR}"
4564
4754
  deleted_files=$(git --no-pager diff --name-only --diff-filter=D --relative 2>/dev/null)
@@ -4566,7 +4756,7 @@ function rebase_conflicts {
4566
4756
  echo -e "${YELLOW}⚠ Some files were deleted in the incoming branch:${ENDCOLOR}"
4567
4757
  echo "$deleted_files" | sed 's/^/\t/'
4568
4758
  echo
4569
- echo -e "Do you want to continue and accept the deletions (y/n)?"
4759
+ echo -e "Do you want to continue and accept the deletions (y/N)?"
4570
4760
  if ! confirm_destructive; then
4571
4761
  echo -e "${YELLOW}Cancelled. Continuing...${ENDCOLOR}"
4572
4762
  continue
@@ -4626,7 +4816,7 @@ function rebase_conflicts {
4626
4816
  if [ "$choice" == "6" ]; then
4627
4817
  echo
4628
4818
  echo -e "${RED}⚠ This will discard the incoming changes for these files.${ENDCOLOR}"
4629
- echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/n)?"
4819
+ echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/N)?"
4630
4820
  if confirm_destructive; then
4631
4821
  echo -e "${YELLOW}Accepting all current changes...${ENDCOLOR}"
4632
4822
  deleted_files=$(git --no-pager diff --name-only --diff-filter=D --relative 2>/dev/null)
@@ -4634,7 +4824,7 @@ function rebase_conflicts {
4634
4824
  echo -e "${YELLOW}⚠ Some files were deleted in the current branch:${ENDCOLOR}"
4635
4825
  echo "$deleted_files" | sed 's/^/\t/'
4636
4826
  echo
4637
- echo -e "Do you want to continue and accept the deletions (y/n)?"
4827
+ echo -e "Do you want to continue and accept the deletions (y/N)?"
4638
4828
  if ! confirm_destructive; then
4639
4829
  echo -e "${YELLOW}Cancelled. Continuing...${ENDCOLOR}"
4640
4830
  continue
@@ -4928,7 +5118,7 @@ function handle_cherry_pick_conflicts {
4928
5118
  ;;
4929
5119
  2)
4930
5120
  echo
4931
- echo -e "Are you sure you want to ${RED}skip${ENDCOLOR} this commit (y/n)?"
5121
+ echo -e "Are you sure you want to ${RED}skip${ENDCOLOR} this commit (y/N)?"
4932
5122
  if confirm_destructive; then
4933
5123
  echo -e "${YELLOW}Skipping commit ${commit_hash::7}${ENDCOLOR}"
4934
5124
  git cherry-pick --skip 2>/dev/null
@@ -4940,7 +5130,7 @@ function handle_cherry_pick_conflicts {
4940
5130
  ;;
4941
5131
  3)
4942
5132
  echo
4943
- echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/n)?"
5133
+ echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/N)?"
4944
5134
  if confirm_destructive; then
4945
5135
  echo -e "${YELLOW}Accepting all incoming changes...${ENDCOLOR}"
4946
5136
  git checkout --theirs . 2>/dev/null
@@ -4961,7 +5151,7 @@ function handle_cherry_pick_conflicts {
4961
5151
  ;;
4962
5152
  4)
4963
5153
  echo
4964
- echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/n)?"
5154
+ echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/N)?"
4965
5155
  if confirm_destructive; then
4966
5156
  echo -e "${YELLOW}Accepting all current changes...${ENDCOLOR}"
4967
5157
  git checkout --ours . 2>/dev/null
@@ -4982,7 +5172,7 @@ function handle_cherry_pick_conflicts {
4982
5172
  ;;
4983
5173
  5)
4984
5174
  echo
4985
- echo -e "Are you sure you want to ${YELLOW}abort${ENDCOLOR} the pull operation (y/n)?"
5175
+ echo -e "Are you sure you want to ${YELLOW}abort${ENDCOLOR} the pull operation (y/N)?"
4986
5176
  if confirm_destructive; then
4987
5177
  echo -e "${YELLOW}Aborting pull commits operation...${ENDCOLOR}"
4988
5178
  git cherry-pick --abort 2>/dev/null
@@ -5570,7 +5760,7 @@ function squash_script {
5570
5760
  size_warning=" ${RED}(large — model accuracy may degrade above ~${SQUASH_MAX_COMMITS} commits)${ENDCOLOR}"
5571
5761
  fi
5572
5762
  echo -e "${YELLOW}Will analyze ${BOLD}${commit_count}${NORMAL}${YELLOW} commits with AI.${ENDCOLOR}${size_warning}"
5573
- echo -e "Continue (y/n)?"
5763
+ echo -e "Continue (Y/n)?"
5574
5764
  local pre_choice
5575
5765
  read -n 1 -s pre_choice || prompt_aborted
5576
5766
  if ! is_yes "$pre_choice"; then
@@ -5620,7 +5810,7 @@ function squash_script {
5620
5810
  if [ -z "$mode_yes" ]; then
5621
5811
  echo -e "${RED}⚠ Applying this plan rewrites your commit history.${ENDCOLOR}"
5622
5812
  echo -e "${CYAN}💡 Recover the original branch with ${BOLD}gitb undo rebase${NORMAL}${CYAN} if needed.${ENDCOLOR}"
5623
- echo -e "Are you sure you want to proceed (y/n)?"
5813
+ echo -e "Are you sure you want to proceed (y/N)?"
5624
5814
  if ! confirm_destructive; then
5625
5815
  echo -e "${YELLOW}Cancelled.${ENDCOLOR}"
5626
5816
  exit 0
@@ -6166,7 +6356,7 @@ function push_script {
6166
6356
  echo -e "${BLUE}Branch ${YELLOW}${current_branch}${BLUE} does not exist on ${YELLOW}${origin_name}${BLUE} yet.${ENDCOLOR}"
6167
6357
  echo
6168
6358
  if [ -z "${fast}" ]; then
6169
- echo -e "Do you want to push the new branch to ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (y/n)?"
6359
+ echo -e "Do you want to push the new branch to ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (Y/n)?"
6170
6360
  yes_no_choice "Pushing..."
6171
6361
  else
6172
6362
  echo -e "${YELLOW}Pushing...${ENDCOLOR}"
@@ -6229,7 +6419,7 @@ function push_script {
6229
6419
  if [ "${current_branch}" == "${main_branch}" ]; then
6230
6420
  echo -e "${RED}⚠ You are about to push directly to the default branch ${YELLOW}${main_branch}${RED}.${ENDCOLOR}"
6231
6421
  fi
6232
- echo -e "Do you want to push${RED}${force_label}${ENDCOLOR} these commits to ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (y/n)?"
6422
+ echo -e "Do you want to push${RED}${force_label}${ENDCOLOR} these commits to ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (Y/n)?"
6233
6423
  yes_no_choice "Pushing..."
6234
6424
  else
6235
6425
  echo -e "${YELLOW}Pushing...${ENDCOLOR}"
@@ -6243,7 +6433,7 @@ function push_script {
6243
6433
  echo
6244
6434
  pull "$current_branch" "$origin_name" "$editor" "rebase"
6245
6435
  else
6246
- echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (y/n)?"
6436
+ echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (Y/n)?"
6247
6437
  yes_no_choice "Pulling..."
6248
6438
  pull "$current_branch" "$origin_name" "$editor"
6249
6439
  fi
@@ -7225,6 +7415,19 @@ function handle_ai_commit_generation {
7225
7415
  if [ $? -ne 0 ] || [ -z "$ai_commit_message" ]; then
7226
7416
  echo
7227
7417
  echo -e "${RED}✗ Cannot generate AI commit message.${ENDCOLOR}"
7418
+ echo
7419
+ read_key choice "Continue with a manual commit message? (Y/n) " || choice="n"
7420
+ echo
7421
+ if is_yes "$choice"; then
7422
+ if [ "$ai_mode" = "subject" ]; then
7423
+ echo
7424
+ fi
7425
+ echo -e "${YELLOW}Falling back to manual commit message creation...${ENDCOLOR}"
7426
+ if [ "$ai_mode" = "subject" ]; then
7427
+ echo
7428
+ fi
7429
+ return
7430
+ fi
7228
7431
  cleanup_on_exit "$git_add"
7229
7432
  exit 1
7230
7433
  fi
@@ -7289,7 +7492,7 @@ ${ai_commit_message}"
7289
7492
  echo
7290
7493
  echo -e "${YELLOW}⚠ Commit message cannot be empty.${ENDCOLOR}"
7291
7494
  echo
7292
- read -n 1 -p "Try again? (y/n) " -s -e choice || choice="n"
7495
+ read -n 1 -p "Try again? (Y/n) " -s -e choice || choice="n"
7293
7496
  if ! is_yes "$choice"; then
7294
7497
  cleanup_on_exit "$git_add"
7295
7498
  rm -f "$commitmsg_file"
@@ -7696,7 +7899,7 @@ function commit_script {
7696
7899
  if [ -n "$saved_git_add" ]; then
7697
7900
  echo
7698
7901
  echo -e "${YELLOW}Found previous git add arguments:${ENDCOLOR} ${BOLD}$saved_git_add${ENDCOLOR}"
7699
- read_key choice "Use them? (y/n) " || choice="n"
7902
+ read_key choice "Use them? (Y/n) " || choice="n"
7700
7903
  echo
7701
7904
  if is_yes "$choice"; then
7702
7905
  git add $saved_git_add
@@ -8087,7 +8290,7 @@ ${staged_with_tab}
8087
8290
  echo
8088
8291
  echo -e "${YELLOW}⚠ Commit message cannot be empty.${ENDCOLOR}"
8089
8292
  echo
8090
- read -n 1 -p "Try again? (y/n) " -s -e choice || choice="n"
8293
+ read -n 1 -p "Try again? (Y/n) " -s -e choice || choice="n"
8091
8294
  if ! is_yes "$choice"; then
8092
8295
  cleanup_on_exit "$git_add"
8093
8296
  rm -f "$commitmsg_file"
@@ -8293,7 +8496,7 @@ function edit_branch_name {
8293
8496
  if [ "$old_name" = "$main_branch" ]; then
8294
8497
  echo -e "${YELLOW}⚠ ${BOLD}${old_name}${NORMAL} is the configured main branch.${ENDCOLOR}"
8295
8498
  echo -e "Renaming it will leave ${YELLOW}gitbasher.branch${ENDCOLOR} pointing at the old name."
8296
- printf "Rename anyway? (y/n) "
8499
+ printf "Rename anyway? (y/N) "
8297
8500
  yes_no_choice_strict
8298
8501
  echo
8299
8502
  fi
@@ -8316,7 +8519,7 @@ function edit_branch_name {
8316
8519
  echo
8317
8520
  echo -e "${YELLOW}${BOLD}${old_name}${NORMAL}${YELLOW} also exists on ${origin_name}.${ENDCOLOR}"
8318
8521
  echo -e "Push ${BOLD}${new_name}${NORMAL} and delete the old remote branch?"
8319
- printf "(y/n) "
8522
+ printf "(y/N) "
8320
8523
  yes_no_choice_strict
8321
8524
  echo
8322
8525
  local push_out
@@ -8481,7 +8684,7 @@ function branch_script {
8481
8684
  done
8482
8685
  echo
8483
8686
  echo -e "${RED}⚠ These branches will be force-deleted (${BLUE}git branch -D${RED}).${ENDCOLOR}"
8484
- echo -e "Are you sure you want to delete them (y/n)?"
8687
+ echo -e "Are you sure you want to delete them (y/N)?"
8485
8688
  yes_no_choice_strict "Deleting gone branches..."
8486
8689
  for branch in "${gone_branches[@]}"; do
8487
8690
  delete_output=$(git branch -D "$branch" 2>&1)
@@ -8501,7 +8704,7 @@ function branch_script {
8501
8704
  fi
8502
8705
  if [[ -n "${tag}" ]]; then
8503
8706
  echo -e "${YELLOW}Do you want to fetch remote tags first?${ENDCOLOR}"
8504
- read -n 1 -p "Fetch remote? (y/n) " choice || choice="n"
8707
+ read -n 1 -p "Fetch remote? (Y/n) " choice || choice="n"
8505
8708
  echo
8506
8709
  if [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
8507
8710
  echo
@@ -8584,7 +8787,7 @@ function branch_script {
8584
8787
  elif [[ -z "$new" ]] && [[ -n "$delete" ]] && [[ -z "$tag" ]]; then
8585
8788
  echo -e "${YELLOW}Clean up local branches that no longer exist on the remote?${ENDCOLOR}"
8586
8789
  echo
8587
- printf "Continue (y/n)? "
8790
+ printf "Continue (Y/n)? "
8588
8791
  while [ true ]; do
8589
8792
  read -n 1 -s choice || prompt_aborted
8590
8793
  if is_yes "$choice"; then
@@ -8614,7 +8817,7 @@ function branch_script {
8614
8817
  done
8615
8818
  echo
8616
8819
  echo -e "${RED}⚠ These branches will be force-deleted (${BLUE}git branch -D${RED}).${ENDCOLOR}"
8617
- printf "Are you sure you want to delete them (y/n)? "
8820
+ printf "Are you sure you want to delete them (y/N)? "
8618
8821
  if confirm_destructive; then
8619
8822
  echo
8620
8823
  for branch in "${branches_to_delete[@]}"; do
@@ -8654,7 +8857,7 @@ function branch_script {
8654
8857
  do
8655
8858
  printf "\t${merged_branches_without_main[index]}\n"
8656
8859
  done
8657
- printf "\nDelete these merged branches (y/n)? "
8860
+ printf "\nDelete these merged branches (Y/n)? "
8658
8861
  while [ true ]; do
8659
8862
  read -n 1 -s choice || prompt_aborted
8660
8863
  if is_yes "$choice"; then
@@ -8722,7 +8925,7 @@ function branch_script {
8722
8925
  done
8723
8926
  echo
8724
8927
  echo -e "${RED}⚠ These will be force-deleted (${BLUE}git branch -D${RED}).${ENDCOLOR}"
8725
- printf "Are you sure (y/n)? "
8928
+ printf "Are you sure (y/N)? "
8726
8929
  if confirm_destructive; then
8727
8930
  echo
8728
8931
  for branch in "${squash_merged_branches[@]}"; do
@@ -8750,7 +8953,7 @@ function branch_script {
8750
8953
  elif [[ ${delete_output} == *"is not fully merged"* ]]; then
8751
8954
  echo -e "${YELLOW}⚠ Branch '$branch_name' is not fully merged.${ENDCOLOR}"
8752
8955
  echo -e "${RED}⚠ Force-delete (${BLUE}git branch -D${RED}) will discard unmerged commits.${ENDCOLOR}"
8753
- printf "Force-delete this branch (y/n)? "
8956
+ printf "Force-delete this branch (y/N)? "
8754
8957
  confirm_destructive
8755
8958
  case $? in
8756
8959
  0)
@@ -8774,7 +8977,7 @@ function branch_script {
8774
8977
  fi
8775
8978
  if git rev-parse --verify --quiet "refs/remotes/$origin_name/$branch_name" >/dev/null 2>&1; then
8776
8979
  echo
8777
- printf "Also delete this branch on ${origin_name} (y/n)? "
8980
+ printf "Also delete this branch on ${origin_name} (y/N)? "
8778
8981
  if confirm_destructive; then
8779
8982
  echo
8780
8983
  echo -e "${YELLOW}Deleting from remote...${ENDCOLOR}"
@@ -8945,7 +9148,7 @@ function branch_script {
8945
9148
  fi
8946
9149
  if [ -n "$origin_name" ]; then
8947
9150
  echo
8948
- echo -e "Do you want to push the new branch to ${YELLOW}${origin_name}/${branch_name}${ENDCOLOR} (y/n)?"
9151
+ echo -e "Do you want to push the new branch to ${YELLOW}${origin_name}/${branch_name}${ENDCOLOR} (Y/n)?"
8949
9152
  yes_no_choice "Pushing..."
8950
9153
  current_branch="$branch_name"
8951
9154
  push -u
@@ -9184,7 +9387,7 @@ function tag_script {
9184
9387
  fi
9185
9388
  if [ -n "${delete}" ] && [ -z "$select" ]; then
9186
9389
  echo
9187
- echo -e "${YELLOW}Do you really want to delete all local tags (y/n)?${ENDCOLOR}"
9390
+ echo -e "${YELLOW}Do you really want to delete all local tags (y/N)?${ENDCOLOR}"
9188
9391
  yes_no_choice_strict "\nDeleting all local tags..."
9189
9392
  local _da_failed=0
9190
9393
  while IFS= read -r _da_tag; do
@@ -9225,7 +9428,7 @@ function tag_script {
9225
9428
  check_code $? "$delete_result" "delete tag"
9226
9429
  echo -e "${GREEN}✓ Deleted tag '${tag_name}'${ENDCOLOR}"
9227
9430
  echo
9228
- echo -e "Also delete this tag on ${YELLOW}${origin_name}${ENDCOLOR} (y/n)?"
9431
+ echo -e "Also delete this tag on ${YELLOW}${origin_name}${ENDCOLOR} (y/N)?"
9229
9432
  yes_no_choice_strict "\nDeleting..."
9230
9433
  push_tag $tag_name "true"
9231
9434
  exit
@@ -9286,7 +9489,7 @@ function tag_script {
9286
9489
  echo
9287
9490
  echo -e "${YELLOW}⚠ Tag message cannot be empty.${ENDCOLOR}"
9288
9491
  echo
9289
- read -n 1 -p "Try again? (y/n) " -s -e choice || choice="n"
9492
+ read -n 1 -p "Try again? (Y/n) " -s -e choice || choice="n"
9290
9493
  if ! is_yes "$choice"; then
9291
9494
  rm -f "$tag_file"
9292
9495
  exit
@@ -9324,7 +9527,7 @@ function tag_script {
9324
9527
  echo -e "$tag_message"
9325
9528
  fi
9326
9529
  echo
9327
- echo -e "Do you want to push it to ${YELLOW}${origin_name}${ENDCOLOR} (y/n)?"
9530
+ echo -e "Do you want to push it to ${YELLOW}${origin_name}${ENDCOLOR} (Y/n)?"
9328
9531
  yes_no_choice "\nPushing..."
9329
9532
  push_tag $tag_name
9330
9533
  }
@@ -9418,7 +9621,7 @@ function reset_script {
9418
9621
  else
9419
9622
  echo -e "${YELLOW}⚠ This will move HEAD; reset changes will be left unstaged.${ENDCOLOR}"
9420
9623
  fi
9421
- echo -e "Are you sure you want to continue (y/n)?"
9624
+ echo -e "Are you sure you want to continue (y/N)?"
9422
9625
  yes_no_choice_strict "Resetting..."
9423
9626
  reset_output=$(git reset "$args" "$move_ref" 2>&1)
9424
9627
  check_code $? "$reset_output" "reset"
@@ -9714,7 +9917,7 @@ function stash_script {
9714
9917
  fi
9715
9918
  echo
9716
9919
  echo -e "${RED}⚠ Dropping stash $selected_stash cannot be undone.${ENDCOLOR}"
9717
- echo -e "Are you sure you want to drop it (y/n)?"
9920
+ echo -e "Are you sure you want to drop it (y/N)?"
9718
9921
  yes_no_choice_strict "Dropping stash"
9719
9922
  drop_output=$(git stash drop "$selected_stash" 2>&1)
9720
9923
  drop_code=$?
@@ -9829,7 +10032,7 @@ function cherry_script {
9829
10032
  echo -e "Files with changes:"
9830
10033
  git status --porcelain | sed 's/^/ /'
9831
10034
  echo
9832
- echo -e "Continue with cherry-pick (may cause conflicts) (y/n)?"
10035
+ echo -e "Continue with cherry-pick (may cause conflicts) (Y/n)?"
9833
10036
  yes_no_choice "Proceeding..."
9834
10037
  echo
9835
10038
  fi
@@ -9909,7 +10112,7 @@ function handle_cherry_pick_conflicts {
9909
10112
  fi
9910
10113
  if [[ $output == *"The previous cherry-pick is now empty"* ]]; then
9911
10114
  echo -e "${YELLOW}This commit is empty after cherry-pick (changes already applied).${ENDCOLOR}"
9912
- echo -e "Skip it (y/n)?"
10115
+ echo -e "Skip it (y/N)?"
9913
10116
  yes_no_choice_strict "Skipping empty commit..."
9914
10117
  skip_output=$(git cherry-pick --skip 2>&1)
9915
10118
  if [ $? == 0 ]; then
@@ -10029,7 +10232,7 @@ function choose_commits_interactive {
10029
10232
  echo -e " ${commit::7} - $message"
10030
10233
  done
10031
10234
  echo
10032
- echo -e "Proceed with cherry-pick? (y/n)"
10235
+ echo -e "Proceed with cherry-pick? (Y/n)"
10033
10236
  yes_no_choice "Proceeding with cherry-pick..."
10034
10237
  local success_count=0
10035
10238
  local total_count=${#selected_commits[@]}
@@ -10272,7 +10475,7 @@ function undo_commit {
10272
10475
  echo -e "\t$cancelled_commit"
10273
10476
  echo
10274
10477
  echo -e "This will undo the last commit but ${GREEN}keep all changes staged${ENDCOLOR}"
10275
- echo -e "Do you want to continue (y/n)?"
10478
+ echo -e "Do you want to continue (y/N)?"
10276
10479
  yes_no_choice_strict "Undoing commit..."
10277
10480
  reset_output=$(git reset --soft HEAD~1 2>&1)
10278
10481
  reset_code=$?
@@ -10321,7 +10524,7 @@ function undo_amend {
10321
10524
  echo -e "${YELLOW}Current (amended):${ENDCOLOR}\t$current_commit"
10322
10525
  echo -e "${GREEN}Restore to:${ENDCOLOR}\t\t$pre_amend_commit"
10323
10526
  echo
10324
- echo -e "Do you want to undo the amend (y/n)?"
10527
+ echo -e "Do you want to undo the amend (y/N)?"
10325
10528
  yes_no_choice_strict "Undoing amend..."
10326
10529
  reset_output=$(git reset --soft "$pre_amend_ref" 2>&1)
10327
10530
  reset_code=$?
@@ -10341,7 +10544,7 @@ function undo_amend {
10341
10544
  function undo_merge {
10342
10545
  if [ -f "$(git rev-parse --git-dir)/MERGE_HEAD" ]; then
10343
10546
  echo -e "${YELLOW}⚠ A merge is in progress.${ENDCOLOR}"
10344
- echo -e "Abort the ongoing merge (y/n)?"
10547
+ echo -e "Abort the ongoing merge (y/N)?"
10345
10548
  yes_no_choice_strict "Aborting merge..."
10346
10549
  abort_output=$(git merge --abort 2>&1)
10347
10550
  abort_code=$?
@@ -10370,7 +10573,7 @@ function undo_merge {
10370
10573
  echo -e "${GREEN}Restore to:${ENDCOLOR}\t$orig_commit"
10371
10574
  echo
10372
10575
  echo -e "${RED}⚠ This will discard the merge commit and all merge changes.${ENDCOLOR}"
10373
- echo -e "Are you sure you want to undo the merge (y/n)?"
10576
+ echo -e "Are you sure you want to undo the merge (y/N)?"
10374
10577
  yes_no_choice_strict "Undoing merge..."
10375
10578
  reset_output=$(git reset --merge ORIG_HEAD 2>&1)
10376
10579
  reset_code=$?
@@ -10387,7 +10590,7 @@ function undo_merge {
10387
10590
  function undo_rebase {
10388
10591
  if [ -d "$(git rev-parse --git-dir)/rebase-merge" ] || [ -d "$(git rev-parse --git-dir)/rebase-apply" ]; then
10389
10592
  echo -e "${YELLOW}⚠ A rebase is in progress.${ENDCOLOR}"
10390
- echo -e "Abort the ongoing rebase (y/n)?"
10593
+ echo -e "Abort the ongoing rebase (y/N)?"
10391
10594
  yes_no_choice_strict "Aborting rebase..."
10392
10595
  abort_output=$(git rebase --abort 2>&1)
10393
10596
  abort_code=$?
@@ -10418,7 +10621,7 @@ function undo_rebase {
10418
10621
  exit 1
10419
10622
  fi
10420
10623
  echo -e "${RED}⚠ This will discard all rebase changes and restore the original branch state.${ENDCOLOR}"
10421
- echo -e "Are you sure you want to undo the rebase (y/n)?"
10624
+ echo -e "Are you sure you want to undo the rebase (y/N)?"
10422
10625
  yes_no_choice_strict "Undoing rebase..."
10423
10626
  reset_output=$(git reset --hard ORIG_HEAD 2>&1)
10424
10627
  reset_code=$?
@@ -10442,7 +10645,7 @@ function undo_stash {
10442
10645
  git_status
10443
10646
  echo
10444
10647
  echo -e "This will stash all current changes to undo the last stash pop/apply."
10445
- echo -e "Continue (y/n)?"
10648
+ echo -e "Continue (y/N)?"
10446
10649
  yes_no_choice_strict "Re-stashing changes..."
10447
10650
  stash_output=$(git stash push -m "undo: re-stashed changes" --include-untracked 2>&1)
10448
10651
  stash_code=$?
@@ -11492,7 +11695,7 @@ function log_commit_actions {
11492
11695
  choice=$((10#$choice))
11493
11696
  local file="${files[choice - 1]}"
11494
11697
  echo -e "${RED}This will overwrite '${file}' in your working tree with its state from ${hash}.${ENDCOLOR}"
11495
- printf "Restore it (y/n)? "
11698
+ printf "Restore it (y/N)? "
11496
11699
  if confirm_destructive; then
11497
11700
  result=$(git checkout "$hash" -- "$file" 2>&1)
11498
11701
  check_code $? "$result" "restore"
@@ -12709,7 +12912,7 @@ function _do_worktree_remove {
12709
12912
  echo -e "${RED}${rm_output}${ENDCOLOR}"
12710
12913
  echo
12711
12914
  echo -e "${YELLOW}Force remove this worktree? (uncommitted changes will be lost)${ENDCOLOR}"
12712
- echo -e "Do you want to continue (y/n)?"
12915
+ echo -e "Do you want to continue (y/N)?"
12713
12916
  yes_no_choice_strict "Force removing worktree..."
12714
12917
  rm_output=$(LC_ALL=C git worktree remove --force --force "$selected_worktree_path" 2>&1)
12715
12918
  rm_code=$?
@@ -12742,7 +12945,7 @@ function worktree_prune {
12742
12945
  echo -e "${YELLOW}Records that would be removed:${ENDCOLOR}"
12743
12946
  echo "$dry_run"
12744
12947
  echo
12745
- echo -e "${YELLOW}Continue? (y/n)${ENDCOLOR}"
12948
+ echo -e "${YELLOW}Continue? (Y/n)${ENDCOLOR}"
12746
12949
  yes_no_choice "Pruning..."
12747
12950
  local prune_output
12748
12951
  prune_output=$(git worktree prune --verbose 2>&1)
@@ -13422,7 +13625,7 @@ function create_hook {
13422
13625
  if [ -f "$hook_file" ]; then
13423
13626
  echo -e "${YELLOW}⚠ Hook '$hook_type' already exists.${ENDCOLOR}"
13424
13627
  echo -e "${RED}⚠ Overwriting will replace its contents.${ENDCOLOR}"
13425
- echo -e "Overwrite (y/n)?"
13628
+ echo -e "Overwrite (y/N)?"
13426
13629
  if ! confirm_destructive; then
13427
13630
  echo -e "${YELLOW}Hook creation cancelled.${ENDCOLOR}"
13428
13631
  return 1
@@ -13468,7 +13671,7 @@ fi
13468
13671
  # lines for any binary blob that happens to be staged.
13469
13672
  if git diff --cached --name-only -z | xargs -0 grep -l --binary-files=without-match \"TODO\\|FIXME\" 2>/dev/null; then
13470
13673
  echo \"⚠️ Warning: Found TODO/FIXME comments in staged files\"
13471
- echo \"Continue anyway? (y/n)\"
13674
+ echo \"Continue anyway? (y/N)\"
13472
13675
  read -n 1 answer
13473
13676
  echo
13474
13677
  if [ \"\$answer\" != \"y\" ] && [ \"\$answer\" != \"Y\" ]; then
@@ -13573,7 +13776,7 @@ exit 0"
13573
13776
  echo -e "${GREEN}✓ Created executable hook: $hook_type${ENDCOLOR}"
13574
13777
  echo -e "${YELLOW}Hook file: $hook_file${ENDCOLOR}"
13575
13778
  echo
13576
- echo -e "Do you want to ${BLUE}edit${ENDCOLOR} the hook now? (y/n)"
13779
+ echo -e "Do you want to ${BLUE}edit${ENDCOLOR} the hook now? (Y/n)"
13577
13780
  read -n 1 -s choice || prompt_aborted
13578
13781
  echo
13579
13782
  if is_yes "$choice"; then
@@ -13644,7 +13847,7 @@ function remove_hook {
13644
13847
  fi
13645
13848
  echo -e "${RED}⚠ Deleting hook '$hook_type' cannot be undone.${ENDCOLOR}"
13646
13849
  echo -e "${GRAY}File: $hook_file${ENDCOLOR}"
13647
- echo -e "Are you sure you want to delete it (y/n)?"
13850
+ echo -e "Are you sure you want to delete it (y/N)?"
13648
13851
  if confirm_destructive; then
13649
13852
  rm "$hook_file"
13650
13853
  echo -e "${GREEN}✓ Removed hook: $hook_type${ENDCOLOR}"
@@ -13685,7 +13888,7 @@ function remove_all_hooks {
13685
13888
  fi
13686
13889
  echo
13687
13890
  echo -e "${RED}⚠ This will permanently delete $hook_count hooks.${ENDCOLOR}"
13688
- echo -e "${YELLOW}Are you absolutely sure (y/n)?${ENDCOLOR}"
13891
+ echo -e "${YELLOW}Are you absolutely sure (Y/n)?${ENDCOLOR}"
13689
13892
  read -n 1 -p "Your choice: " choice < /dev/tty || choice="0"
13690
13893
  echo
13691
13894
  echo
@@ -13816,7 +14019,7 @@ function install_samples {
13816
14019
  fi
13817
14020
  echo
13818
14021
  echo -e "${YELLOW}Ready to install $sample_count sample hooks.${ENDCOLOR}"
13819
- echo -e "Continue (y/n)?"
14022
+ echo -e "Continue (Y/n)?"
13820
14023
  read -n 1 -p "Your choice: " choice < /dev/tty || choice="n"
13821
14024
  echo
13822
14025
  echo
@@ -14264,7 +14467,7 @@ function origin_remove {
14264
14467
  echo -e "URL: ${GRAY}$existing${ENDCOLOR}"
14265
14468
  echo
14266
14469
  echo -e "${RED}⚠ This will remove the remote (you can re-add it later).${ENDCOLOR}"
14267
- echo -e "Are you sure (y/n)?"
14470
+ echo -e "Are you sure (y/N)?"
14268
14471
  yes_no_choice_strict "" "true"
14269
14472
  git remote remove "$target"
14270
14473
  echo
@@ -14423,7 +14626,7 @@ function clone_script {
14423
14626
  echo
14424
14627
  if [ -z "$GITBASHER_TEST_MODE" ] && [ -t 0 ]; then
14425
14628
  local clone_cd_choice
14426
- echo -e "${YELLOW}Enter the new repo now (y/n)?${ENDCOLOR}"
14629
+ echo -e "${YELLOW}Enter the new repo now (Y/n)?${ENDCOLOR}"
14427
14630
  read -n 1 -s clone_cd_choice
14428
14631
  echo
14429
14632
  if is_yes "$clone_cd_choice"; then
@@ -14765,9 +14968,9 @@ function update_script {
14765
14968
  fi
14766
14969
  echo -e "Install location: ${BLUE}${install_path}${ENDCOLOR}"
14767
14970
  if [ $cmp -eq 2 ]; then
14768
- echo -e "Update ${YELLOW}${current_version}${ENDCOLOR} → ${GREEN}${latest_version}${ENDCOLOR}? (y/n)"
14971
+ echo -e "Update ${YELLOW}${current_version}${ENDCOLOR} → ${GREEN}${latest_version}${ENDCOLOR}? (Y/n)"
14769
14972
  else
14770
- echo -e "Reinstall ${GREEN}${latest_version}${ENDCOLOR} over the current binary? (y/n)"
14973
+ echo -e "Reinstall ${GREEN}${latest_version}${ENDCOLOR} over the current binary? (Y/n)"
14771
14974
  fi
14772
14975
  yes_no_choice "Updating..."
14773
14976
  local tmp_path
@@ -15321,7 +15524,7 @@ _gitb() {
15321
15524
  _values 'tag mode' 'annotated[create annotated tag]' 'commit[tag a commit]' 'all[tag everything]' 'push[push tag]' 'push-all[push all tags]' 'delete[delete tag]' 'delete-all[delete all]' 'list[list tags]' 'remote[remote tags]' 'help[show help]'
15322
15525
  ;;
15323
15526
  config|cf|cfg|conf)
15324
- _values 'config key' 'default[default branch]' 'separator[separator char]' 'editor[editor]' 'ticket[ticket prefix]' 'scopes[scope list]' 'ai[AI key]' 'provider[AI provider]' 'model[AI model]' 'proxy[AI proxy]' 'history[AI history]' 'diff[AI diff payload]' 'push-size[warn on large pushes]' 'delete[unset a key]' 'user[user name/email]' 'auto[manage tab completion]' 'help[show help]'
15527
+ _values 'config key' 'default[default branch]' 'separator[separator char]' 'editor[editor]' 'ticket[ticket prefix]' 'scopes[scope list]' 'ai[AI setup wizard]' 'key[AI API key]' 'provider[AI provider]' 'model[AI model]' 'proxy[AI proxy]' 'history[AI history]' 'diff[AI diff payload]' 'push-size[warn on large pushes]' 'delete[unset a key]' 'user[user name/email]' 'auto[manage tab completion]' 'help[show help]'
15325
15528
  ;;
15326
15529
  undo|un)
15327
15530
  _values 'undo target' 'commit[undo last commit]' 'amend[undo amend]' 'merge[undo merge]' 'rebase[undo rebase]' 'stash[undo stash]' 'help[show help]'
@@ -15523,7 +15726,7 @@ complete -c gitb -n "$__gitb_branch" -a "list remote main new newd delete prev r
15523
15726
  set -l __gitb_tag "__gitb_using_cmd tag t tg; and __gitb_at_position 2"
15524
15727
  complete -c gitb -n "$__gitb_tag" -a "annotated commit all push push-all delete delete-all list remote help"
15525
15728
  set -l __gitb_config "__gitb_using_cmd config cf cfg conf; and __gitb_at_position 2"
15526
- complete -c gitb -n "$__gitb_config" -a "default separator editor ticket scopes ai provider model proxy history diff push-size delete user auto help"
15729
+ complete -c gitb -n "$__gitb_config" -a "default separator editor ticket scopes ai key provider model proxy history diff push-size delete user auto help"
15527
15730
  set -l __gitb_undo "__gitb_using_cmd undo un; and __gitb_at_position 2"
15528
15731
  complete -c gitb -n "$__gitb_undo" -a "commit amend merge rebase stash help"
15529
15732
  set -l __gitb_reset "__gitb_using_cmd reset res; and __gitb_at_position 2"