22source ~ /.bash_profile
33
44# Configuration
5- MAX_DIFF_CHARS=2000 # Truncate diff to prevent long processing
6- TIMEOUT_SECONDS=120 # Max time to wait for LLM response (14B model can take 60-90s on first token)
7- MAX_COMMIT_LENGTH=50 # Max characters for commit message
5+ MAX_DIFF_CHARS=600 # stripped +/- lines only β keeps 1.5B prefill fast
6+ TIMEOUT_SECONDS=60 # 60s covers multi-file commits on the 1.5B model
7+ MAX_COMMIT_LENGTH=72 # Standard git commit length
88
9- # Squish model selection β set SQUISH_MODEL to target a specific compressed model.
10- # Accepts a name hint (e.g. "14b", "7b") or a full path to a model directory.
11- # Leave empty to let squish auto-detect (uses the first available model in ~/models).
12- # Examples:
13- # SQUISH_MODEL="14b" # matches Qwen2.5-14B-*
14- # SQUISH_MODEL="$HOME/models/Qwen2.5-14B-Instruct-bf16" # explicit path
15- SQUISH_MODEL=" ${SQUISH_MODEL:- } "
9+ # Squish model selection.
10+ # 7B runs at 15-25 tok/s on M3 16GB (comfortably fits in memory).
11+ # 14B is too slow on 16GB β use it only if you have 32GB+ RAM.
12+ # Override: SQUISH_MODEL=14b cm
13+ SQUISH_MODEL=" ${SQUISH_MODEL:- 7b} "
1614
17- # Squish server port β override if you run multiple squish servers concurrently.
18- SQUISH_PORT=" ${SQUISH_PORT:- 8000} "
15+ # Squish server port β must match the port squish is started with.
16+ # CLI default is 11435; override with SQUISH_PORT env var.
17+ SQUISH_PORT=" ${SQUISH_PORT:- 11435} "
1918
2019# Colors
2120RED=' \033[0;31m'
@@ -30,25 +29,35 @@ BOLD='\033[1m'
3029DIM=' \033[2m'
3130NC=' \033[0m' # No Color
3231
33- # Animated spinner with colors
34- spinner () {
32+ # Snake spinner β cycling snake glyphs while waiting.
33+ # Usage: snake_spinner PID [label]
34+ # PID = 0 β run until killed (for server-start polling)
35+ # PID > 0 β run until that process exits (for curl/LLM wait)
36+ snake_spinner () {
3537 local pid=$1
36- local delay=0.08
38+ local label= " ${2 :- Generating commit message} "
3739 local frames=(' β£Ύ' ' β£½' ' β£»' ' β’Ώ' ' β‘Ώ' ' β£' ' β£―' ' β£·' )
38- local colors=(" $CYAN " " $BLUE " " $PURPLE " " $CYAN " " $BLUE " " $PURPLE " " $CYAN " " $BLUE " )
39- local i=0
40- local elapsed=0
41-
42- while ps -p $pid > /dev/null 2>&1 ; do
43- printf " \r${colors[$i]}${frames[$i]}${NC} ${WHITE} Generating commit message${NC}${GRAY} ...${NC} ${DIM} (${elapsed} s)${NC} "
44- sleep $delay
45- i=$(( (i + 1 ) % ${# frames[@]} ))
46- elapsed=$( echo " scale=1; $elapsed + $delay " | bc)
47- if ! ps -p $pid > /dev/null 2>&1 ; then
40+ local col_arr=(" $CYAN " " $BLUE " " $PURPLE " " $CYAN " " $BLUE " " $PURPLE " " $CYAN " " $BLUE " )
41+ local nf=${# frames[@]}
42+ local ncol=${# col_arr[@]}
43+ local i=0 step=0
44+ local delay=0.08
45+ local elapsed=" 0.0"
46+
47+ while true ; do
48+ if [ " $pid " -ne 0 ] && ! ps -p " $pid " > /dev/null 2>&1 ; then
4849 break
4950 fi
51+
52+ local c=" ${col_arr[$i]} "
53+ printf " \r${c}${frames[$i]}${NC} ${WHITE}${label}${NC}${GRAY} ...${NC} ${DIM} (${elapsed} s)${NC} "
54+
55+ sleep " $delay "
56+ i=$(( (i + 1 ) % nf ))
57+ step=$(( step + 1 ))
58+ elapsed=$( echo " scale=1; $step * $delay " | bc)
5059 done
51- printf " \r${GREEN} β${NC} ${WHITE} Done!${NC} \n"
60+ printf " \r${GREEN} β${NC} ${WHITE} Done!${NC} \n"
5261}
5362
5463# Status messages
@@ -152,11 +161,17 @@ if [ -n "$SQUISH_PORT" ]; then
152161fi
153162
154163# ββ Debug: squish availability βββββββββββββββββββββββββββββββββββββββββββ
155- # squish may be a zsh alias (not visible to bash scripts) β fall back to
156- # calling cli.py directly with python3 if the command isn't on PATH.
164+ # squish is typically a zsh alias (not visible to bash scripts) β fall back to
165+ # calling squish/cli.py directly with python3 if the command isn't on PATH.
166+ # Also export SQUISH_MODELS_DIR so the CLI finds models in the squish repo.
167+ export SQUISH_MODELS_DIR=" ${SQUISH_MODELS_DIR:- $HOME / squish/ models} " _SQUISH_CLI=" /Users/wscholl/squish/squish/cli.py"
157168SQUISH_BIN=$( command -v squish 2> /dev/null)
158- if [ -z " $SQUISH_BIN " ] && [ -f " /Users/wscholl/squish/cli.py" ]; then
159- SQUISH_BIN=" python3 /Users/wscholl/squish/cli.py"
169+ # command -v may return an alias definition string in some shells β treat that as not-found
170+ if echo " $SQUISH_BIN " | grep -q ' ^alias ' ; then
171+ SQUISH_BIN=" "
172+ fi
173+ if [ -z " $SQUISH_BIN " ] && [ -f " $_SQUISH_CLI " ]; then
174+ SQUISH_BIN=" python3 $_SQUISH_CLI "
160175 print_info " squish binary: ${CYAN} $SQUISH_BIN ${GRAY} (alias not in bash PATH, using direct path)${NC} "
161176elif [ -n " $SQUISH_BIN " ]; then
162177 print_info " squish binary: ${CYAN} $SQUISH_BIN ${NC} "
@@ -183,11 +198,17 @@ if [ -n "$SQUISH_BIN" ]; then
183198 # Start the server in the background and wait for it
184199 $SQUISH_BIN serve ${SQUISH_MODEL: +--model $SQUISH_MODEL } --port " $_port " > /tmp/squish_serve.log 2>&1 &
185200 _serve_pid=$!
201+ # Run snake spinner in background while polling for server readiness
202+ snake_spinner 0 " Starting squish server" &
203+ _snake_pid=$!
186204 _waited=0
187205 while [ $_waited -lt 90 ] && ! nc -z 127.0.0.1 " $_port " 2> /dev/null; do
188206 sleep 1
189207 _waited=$(( _waited + 1 ))
190208 done
209+ kill " $_snake_pid " 2> /dev/null
210+ wait " $_snake_pid " 2> /dev/null
211+ printf " \r \r"
191212 if ! nc -z 127.0.0.1 " $_port " 2> /dev/null; then
192213 print_warning " Server failed to start. Using fallback message."
193214 commit_message=" $fallback_message "
@@ -198,57 +219,85 @@ if [ -n "$SQUISH_BIN" ]; then
198219 echo " "
199220
200221 if [ -z " $commit_message " ]; then
201- # Build a focused prompt β only the stat summary + truncated diff,
202- # no surrounding debug text that could confuse the model.
222+ # Build a focused prompt β stat summary + truncated diff
203223 stat_summary=$( git diff --cached --stat | tail -1)
204224 changed_names=$( git diff --cached --name-only | head -10 | tr ' \n' ' ' )
205225
206- # Write diff to a temp file so Python can read it without any
207- # shell-interpolation escaping issues (newlines, backslashes, etc.)
226+ # Write diff to a temp file so Python reads it safely
208227 echo " $diff " > /tmp/squish_diff.txt
209228
210229 # Use python3 to build the JSON payload β all values go through
211230 # json.dumps() so control characters are properly escaped.
212- PAYLOAD=$( SQUISH_CHANGED=" $changed_names " SQUISH_STAT=" $stat_summary " \
231+ PAYLOAD=$( SQUISH_CHANGED=" $changed_names " SQUISH_STAT=" $stat_summary " MAX_DIFF_CHARS= " $MAX_DIFF_CHARS " \
213232 python3 - << 'PYEOF '
214- import json, os
233+ import json, os, re
234+
235+ def strip_diff(raw: str, max_chars: int) -> str:
236+ """Keep only added/removed lines; skip headers and unchanged context."""
237+ lines = []
238+ for line in raw.splitlines():
239+ # +++ / --- are file headers β skip
240+ if line.startswith("---") or line.startswith("+++"):
241+ continue
242+ # @@ hunk headers β include as section markers but shorten
243+ if line.startswith("@@"):
244+ lines.append(line.split("@@")[-1].strip() or "~~")
245+ continue
246+ # diff --git / index headers β skip
247+ if line.startswith("diff ") or line.startswith("index ") or line.startswith("new file") or line.startswith("deleted file"):
248+ continue
249+ # Keep + / - changed lines, drop unchanged context lines
250+ if line.startswith("+") or line.startswith("-"):
251+ lines.append(line)
252+ return "\n".join(lines)[:max_chars]
253+
254+ diff_raw = open("/tmp/squish_diff.txt").read()
255+ diff = strip_diff(diff_raw, int(os.environ.get("MAX_DIFF_CHARS", "1200")))
256+
215257system = (
216- "You generate concise git commit messages. "
217- "Reply with ONLY the commit message, nothing else. "
218- "Max 50 characters. Imperative mood. No period. No quotes. No markdown."
258+ "You are a git commit message writer. "
259+ "Read the diff and write ONE concise commit message describing what actually changed. "
260+ "Reply with ONLY the commit message β no labels, no filenames, no markdown, no period. "
261+ "Must be a complete thought under 72 characters. Imperative mood (e.g. 'Add', 'Fix', 'Update', 'Remove')."
219262)
220- diff = open("/tmp/squish_diff.txt").read()
221263user = (
222- f"Files changed: {os.environ['SQUISH_CHANGED']}\n"
223- f"Summary: {os.environ['SQUISH_STAT']}\n\n"
224- f"Diff:\n{diff}\n\nCommit message:"
264+ f"Files: {os.environ['SQUISH_CHANGED']}\n"
265+ f"Stat: {os.environ['SQUISH_STAT']}\n\n"
266+ f"Changed lines:\n{diff}\n"
267+ "--- END DIFF ---\n\n"
268+ "Commit message (imperative, < 72 chars):"
225269)
226270print(json.dumps({
227271 "model": "squish",
228272 "messages": [
229273 {"role": "system", "content": system},
230274 {"role": "user", "content": user},
231275 ],
232- "max_tokens": 60 ,
276+ "max_tokens": 50 ,
233277 "temperature": 0.2,
234278 "stream": False,
279+ "stop": ["\n", "\r"],
235280}))
236281PYEOF
237282 )
238283 rm -f /tmp/squish_diff.txt
239284
240285 # Run squish with timeout and spinner
241286 print_step " Asking AI for commit message (Squish local LLM)..."
242- _port=" ${SQUISH_PORT:- 8000} "
287+ _port=" ${SQUISH_PORT:- 11435} "
288+ _llm_start=$( date +%s%3N)
243289 curl -s --max-time $TIMEOUT_SECONDS \
244290 -X POST " http://127.0.0.1:${_port} /v1/chat/completions" \
245291 -H " Content-Type: application/json" \
292+ -H " Authorization: Bearer ${SQUISH_API_KEY:- squish} " \
246293 -d " $PAYLOAD " 2> /tmp/squish_stderr.txt \
247294 > /tmp/squish_response.txt &
248295 LLM_PID=$!
249- spinner $LLM_PID
296+ snake_spinner $LLM_PID " Generating commit message "
250297 wait $LLM_PID
251298 exit_code=$?
299+ _llm_elapsed=$( echo " scale=2; ($( date +%s%3N) - $_llm_start ) / 1000" | bc)
300+ print_info " model response time: ${CYAN}${_llm_elapsed} s${NC} "
252301
253302 # ββ Debug: result diagnostics βββββββββββββββββββββββββββββββββββββββββ
254303 raw_response=$( cat /tmp/squish_response.txt 2> /dev/null)
0 commit comments