smoothie-code 1.1.0 → 2.0.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.
@@ -0,0 +1,149 @@
1
+ #!/bin/bash
2
+ #
3
+ # gemini-review-hook.sh — BeforeTool hook for exit_plan_mode (Gemini CLI)
4
+ #
5
+ # Same as auto-review-hook.sh but adapted for Gemini CLI:
6
+ # - BeforeTool instead of PreToolUse
7
+ # - exit_plan_mode instead of ExitPlanMode
8
+ # - Reads plan from tool_input.plan_path instead of tailing transcript
9
+ #
10
+ # Caveat: only fires when the agent calls exit_plan_mode itself,
11
+ # not when user manually toggles via /plan or Shift+Tab.
12
+ #
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+
16
+ # Read hook input from stdin
17
+ INPUT=$(cat)
18
+
19
+ # Check if auto-review is enabled in config
20
+ if [ -f "$SCRIPT_DIR/config.json" ]; then
21
+ AUTO_ENABLED=$(node -e "
22
+ try {
23
+ const c = JSON.parse(require('fs').readFileSync('$SCRIPT_DIR/config.json','utf8'));
24
+ console.log(c.auto_review === true ? 'true' : 'false');
25
+ } catch(e) { console.log('false'); }
26
+ " 2>/dev/null)
27
+
28
+ if [ "$AUTO_ENABLED" != "true" ]; then
29
+ echo '{"hookSpecificOutput":{"hookEventName":"BeforeTool","permissionDecision":"allow"}}'
30
+ exit 0
31
+ fi
32
+ fi
33
+
34
+ # Try to read plan from tool_input.plan_path (Gemini provides this)
35
+ PLAN_PATH=$(echo "$INPUT" | python3 -c "
36
+ import sys, json
37
+ try:
38
+ d = json.load(sys.stdin)
39
+ print(d.get('tool_input', {}).get('plan_path', ''))
40
+ except:
41
+ print('')
42
+ " 2>/dev/null)
43
+
44
+ PLAN_CONTEXT=""
45
+ if [ -n "$PLAN_PATH" ] && [ -f "$PLAN_PATH" ]; then
46
+ PLAN_CONTEXT=$(cat "$PLAN_PATH" 2>/dev/null | head -c 16000)
47
+ fi
48
+
49
+ # Fallback: read from transcript if plan_path not available
50
+ if [ -z "$PLAN_CONTEXT" ]; then
51
+ TRANSCRIPT_PATH=$(echo "$INPUT" | python3 -c "
52
+ import sys, json
53
+ try:
54
+ d = json.load(sys.stdin)
55
+ print(d.get('transcript_path', ''))
56
+ except:
57
+ print('')
58
+ " 2>/dev/null)
59
+
60
+ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
61
+ PLAN_CONTEXT=$(tail -c 4000 "$TRANSCRIPT_PATH" 2>/dev/null)
62
+ fi
63
+ fi
64
+
65
+ if [ -z "$PLAN_CONTEXT" ]; then
66
+ echo '{"hookSpecificOutput":{"hookEventName":"BeforeTool","permissionDecision":"allow"}}'
67
+ exit 0
68
+ fi
69
+
70
+ # Build the review prompt
71
+ REVIEW_PROMPT="You are reviewing a plan that Gemini CLI generated. Analyze it for:
72
+ - Missing steps or edge cases
73
+ - Better approaches or optimizations
74
+ - Potential bugs or issues
75
+ - Security concerns
76
+
77
+ Here is the plan:
78
+
79
+ $PLAN_CONTEXT
80
+
81
+ Provide concise, actionable feedback. Focus only on things that should change."
82
+
83
+ # Run the blend (progress shows on stderr, results on stdout)
84
+ BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/review-cli.js" 2>/dev/stderr)
85
+
86
+ # Generate share link (metadata only, no raw content)
87
+ SHARE_URL=""
88
+ SHARE_PARAMS=$(echo "$BLEND_RESULTS" | node -e "
89
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
90
+ try {
91
+ const r=JSON.parse(d);
92
+ const models=r.results.map(m=>m.model).join(',');
93
+ const times=r.results.map(m=>m.elapsed_s||0).join(',');
94
+ const tokens=r.results.map(m=>(m.tokens&&m.tokens.total)||0).join(',');
95
+ console.log('models='+encodeURIComponent(models)+'&times='+encodeURIComponent(times)+'&tokens='+encodeURIComponent(tokens)+'&type=plan');
96
+ } catch { console.log(''); }
97
+ });
98
+ " 2>/dev/null)
99
+
100
+ if [ -n "$SHARE_PARAMS" ]; then
101
+ SHARE_URL=$(curl -s --max-time 5 --connect-timeout 3 -X POST "https://api.smoothiecode.com/api/share" \
102
+ -H "Content-Type: application/json" \
103
+ -d "{\"type\":\"report\",\"data\":\"$SHARE_PARAMS\"}" 2>/dev/null | node -e "
104
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
105
+ try{console.log(JSON.parse(d).url)}catch{console.log('')}
106
+ });
107
+ " 2>/dev/null) || true
108
+ fi
109
+
110
+ if [ -z "$BLEND_RESULTS" ]; then
111
+ echo '{"hookSpecificOutput":{"hookEventName":"BeforeTool","permissionDecision":"allow"}}'
112
+ exit 0
113
+ fi
114
+
115
+ # Build the additionalContext string
116
+ CONTEXT="🧃 Smoothie auto-review results — multiple models reviewed this plan:
117
+
118
+ $BLEND_RESULTS
119
+
120
+ IMPORTANT — You must do TWO things:
121
+
122
+ 1. FIRST, show a Smoothie report table. Use elapsed_s and tokens from each result:
123
+
124
+ 🧃 Smoothie reviewed this plan (N models, Xs total):
125
+
126
+ | Model | Time | Tokens | Verdict | Feedback |
127
+ |-------|------|--------|---------|----------|
128
+ | ModelName | 3.1s | 1,847 | useful | one-line summary |
129
+ | ModelName | 8.2s | 2,104 | generic | one-line summary |
130
+ (skip errored/empty models. Verdict = useful, generic, or irrelevant)
131
+ $([ -n "$SHARE_URL" ] && echo "
132
+ Share this report: $SHARE_URL")
133
+
134
+ 2. THEN proceed with implementation as planned, incorporating valid Smoothie suggestions as you go. Do NOT stop to revise — start coding immediately."
135
+
136
+ # Return: allow exit_plan_mode but inject blend results
137
+ node -e "
138
+ const ctx = $(echo "$CONTEXT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null);
139
+ console.log(JSON.stringify({
140
+ hookSpecificOutput: {
141
+ hookEventName: 'BeforeTool',
142
+ permissionDecision: 'allow',
143
+ permissionDecisionReason: 'Smoothie auto-review completed',
144
+ additionalContext: ctx
145
+ }
146
+ }));
147
+ "
148
+
149
+ exit 0
package/install.sh CHANGED
@@ -11,7 +11,7 @@ B='\033[1m' # bold
11
11
  N='\033[0m' # reset
12
12
 
13
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
- TOTAL_STEPS=7
14
+ TOTAL_STEPS=8
15
15
  STEP=0
16
16
 
17
17
  step() {
@@ -71,11 +71,13 @@ step "Detecting platform"
71
71
  HAS_CLAUDE=$(command -v claude &>/dev/null && echo "yes" || echo "no")
72
72
  HAS_CODEX=$(command -v codex &>/dev/null && echo "yes" || echo "no")
73
73
  HAS_GEMINI=$(command -v gemini &>/dev/null && echo "yes" || echo "no")
74
+ HAS_CURSOR=$([ -d "$HOME/.cursor" ] && echo "yes" || echo "no")
74
75
 
75
76
  DETECTED=()
76
77
  [ "$HAS_CLAUDE" = "yes" ] && DETECTED+=("claude:Claude Code")
77
78
  [ "$HAS_CODEX" = "yes" ] && DETECTED+=("codex:Codex CLI")
78
79
  [ "$HAS_GEMINI" = "yes" ] && DETECTED+=("gemini:Gemini CLI")
80
+ [ "$HAS_CURSOR" = "yes" ] && DETECTED+=("cursor:Cursor")
79
81
 
80
82
  if [ ${#DETECTED[@]} -eq 0 ]; then
81
83
  echo -e " ${Y}No AI CLI detected. Defaulting to Claude Code.${N}"
@@ -102,13 +104,13 @@ if [ "$PLATFORM" != "codex" ]; then
102
104
  step "Setting up Codex ${D}(optional)${N}"
103
105
 
104
106
  echo ""
105
- echo -e " ${D}Codex adds OpenAI's coding model to the blend.${N}"
107
+ echo -e " ${D}Codex adds OpenAI's coding model to the review.${N}"
106
108
  echo -e " ${D}Requires a ChatGPT account. Skip if you only want OpenRouter.${N}"
107
109
  echo ""
108
110
  read -p " Set up Codex? [Y/n]: " SETUP_CODEX
109
111
 
110
112
  if [[ "$SETUP_CODEX" =~ ^[Nn]$ ]]; then
111
- echo -e " ${D}Skipped — blend will use OpenRouter models only${N}"
113
+ echo -e " ${D}Skipped — review will use OpenRouter models only${N}"
112
114
  else
113
115
  if ! command -v codex &>/dev/null; then
114
116
  npm install -g @openai/codex 2>/dev/null &
@@ -154,29 +156,59 @@ step "Choosing models"
154
156
  echo ""
155
157
  node "$SCRIPT_DIR/dist/select-models.js" "$OPENROUTER_KEY" "$SCRIPT_DIR/config.json"
156
158
 
157
- # ─── Step 6: Auto-blend ──────────────────────────────────────────────
159
+ # ─── Step 6: Auto-review ──────────────────────────────────────────────
158
160
  step "Configuring hooks"
159
161
 
160
- chmod +x "$SCRIPT_DIR/plan-hook.sh" "$SCRIPT_DIR/auto-blend-hook.sh" "$SCRIPT_DIR/pr-blend-hook.sh"
162
+ chmod +x "$SCRIPT_DIR/plan-hook.sh" "$SCRIPT_DIR/auto-review-hook.sh" "$SCRIPT_DIR/pr-review-hook.sh"
161
163
 
162
164
  echo ""
163
- echo -e " ${B}Auto-blend${N} reviews every plan with all models before"
165
+ echo -e " ${B}Auto-review${N} reviews every plan with all models before"
164
166
  echo -e " you approve. Adds 30-90s per plan."
165
167
  echo ""
166
- read -p " Enable auto-blend? [y/N]: " AUTO_BLEND
168
+ read -p " Enable auto-review? [y/N]: " AUTO_BLEND
167
169
  if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
168
170
  node -e "
169
171
  const fs = require('fs');
170
172
  const c = JSON.parse(fs.readFileSync('$SCRIPT_DIR/config.json','utf8'));
171
- c.auto_blend = true;
173
+ c.auto_review = true;
172
174
  fs.writeFileSync('$SCRIPT_DIR/config.json', JSON.stringify(c, null, 2));
173
175
  "
174
- echo -e " ${G}✓${N} Auto-blend on"
176
+ echo -e " ${G}✓${N} Auto-review on"
175
177
  else
176
178
  echo -e " ${D}Skipped — toggle in config.json anytime${N}"
177
179
  fi
178
180
 
179
- # ─── Step 7: Wire up ─────────────────────────────────────────────────
181
+ # ─── Step 7: Leaderboard ─────────────────────────────────────────────
182
+ step "Leaderboard"
183
+
184
+ # Leaderboard opt-in
185
+ echo ""
186
+ echo -e " ${B}Leaderboard${N} — your GitHub username and token usage"
187
+ echo -e " appear on the public Smoothie rankings."
188
+ echo -e " ${D}https://smoothiecode.com/leaderboard${N}"
189
+ echo ""
190
+ read -p " Join the leaderboard? [Y/n]: " JOIN_LB
191
+ if [[ ! "$JOIN_LB" =~ ^[Nn]$ ]]; then
192
+ LB_GITHUB=$(git config user.name 2>/dev/null || echo "")
193
+ if [ -z "$LB_GITHUB" ]; then
194
+ read -p " GitHub username: " LB_GITHUB
195
+ else
196
+ read -p " GitHub username [$LB_GITHUB]: " INPUT_LB
197
+ LB_GITHUB="${INPUT_LB:-$LB_GITHUB}"
198
+ fi
199
+ node -e "
200
+ const fs = require('fs');
201
+ const c = JSON.parse(fs.readFileSync('$SCRIPT_DIR/config.json','utf8'));
202
+ c.leaderboard = true;
203
+ c.github = '$LB_GITHUB';
204
+ fs.writeFileSync('$SCRIPT_DIR/config.json', JSON.stringify(c, null, 2));
205
+ "
206
+ echo -e " ${G}✓${N} Joined as $LB_GITHUB"
207
+ else
208
+ echo -e " ${D}Skipped — join anytime: smoothie leaderboard join${N}"
209
+ fi
210
+
211
+ # ─── Step 8: Wire up ─────────────────────────────────────────────────
180
212
  step "Wiring up"
181
213
 
182
214
  if [ "$PLATFORM" = "claude" ]; then
@@ -204,7 +236,7 @@ The user has provided this context/problem:
204
236
  $ARGUMENTS
205
237
 
206
238
  **Step 1 — Blend**
207
- Call `smoothie_blend` with the user's prompt. The MCP server queries all
239
+ Call `smoothie_review` with the user's prompt. The MCP server queries all
208
240
  models in parallel and shows live progress in the terminal. Wait for it to return.
209
241
 
210
242
  **Step 2 — Judge and respond**
@@ -229,7 +261,7 @@ $ARGUMENTS
229
261
  Run `git diff main...HEAD` to get the full branch diff.
230
262
 
231
263
  **Step 2 — Blend**
232
- Call `smoothie_blend` with a prompt asking models to review the diff for:
264
+ Call `smoothie_review` with a prompt asking models to review the diff for:
233
265
  - Bugs, logic errors, edge cases
234
266
  - Security vulnerabilities
235
267
  - Performance issues
@@ -267,20 +299,20 @@ if (!preExists) {
267
299
  matcher: "ExitPlanMode",
268
300
  hooks: [{
269
301
  type: "command",
270
- command: "bash $SCRIPT_DIR/auto-blend-hook.sh",
302
+ command: "bash $SCRIPT_DIR/auto-review-hook.sh",
271
303
  timeout: 600
272
304
  }]
273
305
  });
274
306
  }
275
307
 
276
308
  // Add PR review hook for Bash commands
277
- const bashHookExists = s.hooks.PreToolUse.some(h => h.matcher === 'Bash' && h.hooks?.[0]?.command?.includes('pr-blend-hook'));
309
+ const bashHookExists = s.hooks.PreToolUse.some(h => h.matcher === 'Bash' && h.hooks?.[0]?.command?.includes('pr-review-hook'));
278
310
  if (!bashHookExists) {
279
311
  s.hooks.PreToolUse.push({
280
312
  matcher: "Bash",
281
313
  hooks: [{
282
314
  type: "command",
283
- command: "bash $SCRIPT_DIR/pr-blend-hook.sh",
315
+ command: "bash $SCRIPT_DIR/pr-review-hook.sh",
284
316
  timeout: 600
285
317
  }]
286
318
  });
@@ -322,14 +354,14 @@ if [ "$PLATFORM" = "gemini" ]; then
322
354
 
323
355
  # Gemini slash commands (.toml)
324
356
  cat > "$HOME/.gemini/commands/smoothie.toml" << 'TOML'
325
- description = "Blend this problem across multiple AI models. Gemini judges."
357
+ description = "Review this problem across multiple AI models. Gemini judges."
326
358
 
327
359
  prompt = """
328
360
  You are running Smoothie — a multi-model review session.
329
361
 
330
362
  {{args}}
331
363
 
332
- Step 1 — Call smoothie_blend with the problem text. Wait for results.
364
+ Step 1 — Call smoothie_review with the problem text. Wait for results.
333
365
 
334
366
  Step 2 — You have responses from all models. Do NOT show raw outputs.
335
367
  - If reviewing a problem: give the answer. Mention conflicts in one sentence.
@@ -349,13 +381,91 @@ You are running Smoothie PR Review.
349
381
  {{args}}
350
382
 
351
383
  Step 1 — Run git diff main...HEAD to get the branch diff.
352
- Step 2 — Call smoothie_blend asking models to review the diff for bugs, security, performance.
384
+ Step 2 — Call smoothie_review asking models to review the diff for bugs, security, performance.
353
385
  Step 3 — Summarize findings with file:line references. Be direct.
354
386
  """
355
387
  TOML
356
388
  echo -e " ${G}✓${N} Slash command /smoothie-pr (Gemini)"
389
+
390
+ # Register auto-review hook for Gemini CLI
391
+ chmod +x "$SCRIPT_DIR/gemini-review-hook.sh"
392
+ GEMINI_SETTINGS="$HOME/.gemini/settings.json"
393
+ GEMINI_EXISTING="{}"
394
+ [ -f "$GEMINI_SETTINGS" ] && GEMINI_EXISTING=$(cat "$GEMINI_SETTINGS")
395
+
396
+ node -e "
397
+ const fs = require('fs');
398
+ let s;
399
+ try { s = JSON.parse(\`$GEMINI_EXISTING\`); } catch(e) { s = {}; }
400
+ s.hooks = s.hooks || {};
401
+ s.hooks.BeforeTool = s.hooks.BeforeTool || [];
402
+ const exists = s.hooks.BeforeTool.some(h => h.matcher === 'exit_plan_mode');
403
+ if (!exists) {
404
+ s.hooks.BeforeTool.push({
405
+ matcher: 'exit_plan_mode',
406
+ hooks: [{
407
+ type: 'command',
408
+ command: 'bash $SCRIPT_DIR/gemini-review-hook.sh',
409
+ timeout: 600
410
+ }]
411
+ });
412
+ }
413
+ fs.writeFileSync('$GEMINI_SETTINGS', JSON.stringify(s, null, 2));
414
+ "
415
+ echo -e " ${G}✓${N} Auto-review hook registered (BeforeTool/exit_plan_mode)"
416
+ fi
417
+
418
+ if [ "$PLATFORM" = "cursor" ]; then
419
+ # Register MCP server in Cursor
420
+ CURSOR_MCP="$HOME/.cursor/mcp.json"
421
+ CURSOR_EXISTING="{}"
422
+ [ -f "$CURSOR_MCP" ] && CURSOR_EXISTING=$(cat "$CURSOR_MCP")
423
+
424
+ node -e "
425
+ const fs = require('fs');
426
+ let s;
427
+ try { s = JSON.parse(\`$CURSOR_EXISTING\`); } catch(e) { s = {}; }
428
+ s.mcpServers = s.mcpServers || {};
429
+ s.mcpServers.smoothie = {
430
+ command: 'node',
431
+ args: ['$SCRIPT_DIR/dist/index.js'],
432
+ env: { SMOOTHIE_PLATFORM: 'cursor' }
433
+ };
434
+ fs.writeFileSync('$CURSOR_MCP', JSON.stringify(s, null, 2));
435
+ "
436
+ echo -e " ${G}✓${N} Cursor MCP server registered"
437
+
438
+ # Create Cursor Rule for auto-review (only if auto-review is enabled)
439
+ if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
440
+ mkdir -p "$HOME/.cursor/rules"
441
+ cat > "$HOME/.cursor/rules/smoothie.mdc" << 'CURSORRULE'
442
+ ---
443
+ description: Smoothie multi-model review — runs before significant plans
444
+ alwaysApply: true
445
+ ---
446
+
447
+ Before proposing or implementing any plan that involves significant code changes,
448
+ call the `smoothie_review` MCP tool with a description of what you're about to do.
449
+
450
+ Wait for results, then:
451
+ 1. Show a summary table: Model | Time | Tokens | Verdict | Key feedback
452
+ 2. Incorporate valid suggestions into your plan
453
+ 3. Proceed with implementation
454
+
455
+ Skip the review for trivial changes (typos, single-line fixes, config tweaks).
456
+ CURSORRULE
457
+ echo -e " ${G}✓${N} Cursor Rule created (auto-review)"
458
+ fi
357
459
  fi
358
460
 
461
+ # ─── Setup ~/.smoothie/ (single source of truth for CLI) ─────────────
462
+ mkdir -p "$HOME/.smoothie"
463
+ chmod 700 "$HOME/.smoothie"
464
+ cp "$SCRIPT_DIR/config.json" "$HOME/.smoothie/config.json" 2>/dev/null
465
+ cp "$SCRIPT_DIR/.env" "$HOME/.smoothie/.env" 2>/dev/null
466
+ echo "$SCRIPT_DIR" > "$HOME/.smoothie/.install-path"
467
+ echo -e " ${G}✓${N} Config saved to ~/.smoothie/"
468
+
359
469
  # ─── Done ─────────────────────────────────────────────────────────────
360
470
  MODELS=$(node -e "
361
471
  const d = JSON.parse(require('fs').readFileSync('$SCRIPT_DIR/config.json','utf8'));
@@ -365,11 +475,20 @@ console.log(['Codex', ...d.openrouter_models.map(m=>m.label)].join(' · '));
365
475
  echo ""
366
476
  echo -e " ${D}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${N}"
367
477
  echo ""
368
- echo -e " ${G}${B}Done!${N} Restart Claude Code, then:"
369
- echo ""
370
- echo -e " ${C}/smoothie${N} ${D}<your problem>${N} blend in Claude Code"
371
- if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
372
- echo -e " ${C}auto-blend${N} ${G}on${N} for all plans"
478
+ if [ "$PLATFORM" = "cursor" ]; then
479
+ echo -e " ${G}${B}Done!${N} Restart Cursor, then:"
480
+ echo ""
481
+ echo -e " ${D}Ask Cursor to plan something — it calls smoothie_review via MCP${N}"
482
+ if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
483
+ echo -e " ${C}auto-review${N} ${G}on${N} (via Cursor Rule)"
484
+ fi
485
+ else
486
+ echo -e " ${G}${B}Done!${N} Restart Claude Code, then:"
487
+ echo ""
488
+ echo -e " ${C}/smoothie${N} ${D}<your problem>${N} review in Claude Code"
489
+ if [[ "$AUTO_BLEND" =~ ^[Yy]$ ]]; then
490
+ echo -e " ${C}auto-review${N} ${G}on${N} for all plans"
491
+ fi
373
492
  fi
374
493
  echo -e " ${C}smoothie models${N} manage models"
375
494
  echo ""
package/package.json CHANGED
@@ -1,11 +1,22 @@
1
1
  {
2
2
  "name": "smoothie-code",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "smoothie": "./bin/smoothie"
8
8
  },
9
+ "files": [
10
+ "dist/",
11
+ "bin/",
12
+ "install.sh",
13
+ "auto-review-hook.sh",
14
+ "gemini-review-hook.sh",
15
+ "plan-hook.sh",
16
+ "pr-review-hook.sh",
17
+ "config.json",
18
+ "README.md"
19
+ ],
9
20
  "scripts": {
10
21
  "build": "tsc",
11
22
  "start": "node dist/index.js"
package/plan-hook.sh CHANGED
@@ -32,7 +32,7 @@ IS_PLAN=$(tail -c 3000 "$TRANSCRIPT" | grep -c "Would you like to proceed\|## Pl
32
32
 
33
33
  if [ "$IS_PLAN" -gt 0 ]; then
34
34
  echo ""
35
- echo "🧃 Smoothie: type 'smoothie' in option 5 to blend this plan before approving."
35
+ echo "🧃 Smoothie: type 'smoothie' in option 5 to review this plan before approving."
36
36
  fi
37
37
 
38
38
  exit 0
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  #
3
- # pr-blend-hook.sh — PreToolUse hook for Bash commands
3
+ # pr-review-hook.sh — PreToolUse hook for Bash commands
4
4
  #
5
5
  # Intercepts `gh pr create` commands, runs Smoothie blend on the
6
6
  # branch diff, and injects review results so Claude can revise
@@ -12,11 +12,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  # Read hook input from stdin
13
13
  INPUT=$(cat)
14
14
 
15
- # Check if auto-blend is enabled in config
15
+ # Check if auto-review is enabled in config
16
16
  AUTO_ENABLED=$(node -e "
17
17
  try {
18
18
  const c = JSON.parse(require('fs').readFileSync('$SCRIPT_DIR/config.json','utf8'));
19
- console.log(c.auto_blend === true ? 'true' : 'false');
19
+ console.log(c.auto_review === true ? 'true' : 'false');
20
20
  } catch(e) { console.log('false'); }
21
21
  " 2>/dev/null)
22
22
 
@@ -65,7 +65,37 @@ $DIFF
65
65
 
66
66
  Provide concise, actionable feedback."
67
67
 
68
- BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/blend-cli.js" 2>/dev/stderr)
68
+ BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/review-cli.js" 2>/dev/stderr)
69
+
70
+ # Generate share link (metadata only, no raw content)
71
+ SHARE_URL=""
72
+ SHARE_PARAMS=$(echo "$BLEND_RESULTS" | node -e "
73
+ const fs=require('fs');
74
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
75
+ try {
76
+ const r=JSON.parse(d);
77
+ const models=r.results.map(m=>m.model).join(',');
78
+ const times=r.results.map(m=>m.elapsed_s||0).join(',');
79
+ const tokens=r.results.map(m=>(m.tokens&&m.tokens.total)||0).join(',');
80
+ const responded=r.results.filter(m=>!m.response.startsWith('Error:')&&m.response!=='No response content'&&m.response!=='(empty response)').length;
81
+ let github='',judge='Claude Code';
82
+ try{const c=JSON.parse(fs.readFileSync('$SCRIPT_DIR/config.json','utf8'));github=c.github||'';const p=process.env.SMOOTHIE_PLATFORM||'claude';judge={claude:'Claude Code',gemini:'Gemini CLI',codex:'Codex CLI',cursor:'Cursor'}[p]||'Claude Code';}catch{}
83
+ let params='models='+encodeURIComponent(models)+'&times='+encodeURIComponent(times)+'&tokens='+encodeURIComponent(tokens)+'&type=pr&suggestions='+responded+'&judge='+encodeURIComponent(judge);
84
+ if(github)params+='&user='+encodeURIComponent(github);
85
+ console.log(params);
86
+ } catch { console.log(''); }
87
+ });
88
+ " 2>/dev/null)
89
+
90
+ if [ -n "$SHARE_PARAMS" ]; then
91
+ SHARE_URL=$(curl -s --max-time 5 --connect-timeout 3 -X POST "https://api.smoothiecode.com/api/share" \
92
+ -H "Content-Type: application/json" \
93
+ -d "{\"type\":\"report\",\"data\":\"$SHARE_PARAMS\"}" 2>/dev/null | node -e "
94
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
95
+ try{console.log(JSON.parse(d).url)}catch{console.log('')}
96
+ });
97
+ " 2>/dev/null) || true
98
+ fi
69
99
 
70
100
  if [ -z "$BLEND_RESULTS" ]; then
71
101
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
@@ -79,12 +109,16 @@ $BLEND_RESULTS
79
109
 
80
110
  IMPORTANT — You must do TWO things:
81
111
 
82
- 1. FIRST, show a brief Smoothie summary so the user knows the review ran:
112
+ 1. FIRST, show a Smoothie report table. Use elapsed_s and tokens from each result:
113
+
114
+ 🧃 Smoothie reviewed this PR (N models, Xs total):
83
115
 
84
- 🧃 Smoothie reviewed this PR (N models):
85
- ModelName — one-line key finding
86
- ModelName one-line key finding
87
- (skip models that errored or returned empty)
116
+ | Model | Time | Tokens | Finding |
117
+ |-------|------|--------|---------|
118
+ | ModelName | 3.1s | 1,847 | one-line key finding |
119
+ (skip errored/empty models)
120
+ $([ -n "$SHARE_URL" ] && echo "
121
+ Share this report: $SHARE_URL")
88
122
 
89
123
  2. THEN address any valid issues — fix the code or note them in the PR description."
90
124
 
@@ -1,103 +0,0 @@
1
- #!/bin/bash
2
- #
3
- # auto-blend-hook.sh — PreToolUse hook for ExitPlanMode
4
- #
5
- # Intercepts plan approval, runs Smoothie blend on the plan,
6
- # and injects results back so Claude revises before you see it.
7
- #
8
-
9
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
-
11
- # Read hook input from stdin
12
- INPUT=$(cat)
13
-
14
- # Check if auto-blend is enabled in config
15
- if [ -f "$SCRIPT_DIR/config.json" ]; then
16
- AUTO_ENABLED=$(node -e "
17
- try {
18
- const c = JSON.parse(require('fs').readFileSync('$SCRIPT_DIR/config.json','utf8'));
19
- console.log(c.auto_blend === true ? 'true' : 'false');
20
- } catch(e) { console.log('false'); }
21
- " 2>/dev/null)
22
-
23
- if [ "$AUTO_ENABLED" != "true" ]; then
24
- # Auto-blend disabled — allow ExitPlanMode without intervention
25
- echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
26
- exit 0
27
- fi
28
- fi
29
-
30
- # Extract transcript path
31
- TRANSCRIPT_PATH=$(echo "$INPUT" | python3 -c "
32
- import sys, json
33
- try:
34
- d = json.load(sys.stdin)
35
- print(d.get('transcript_path', ''))
36
- except:
37
- print('')
38
- " 2>/dev/null)
39
-
40
- if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
41
- echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
42
- exit 0
43
- fi
44
-
45
- # Extract the plan from the last ~4000 chars of the transcript
46
- PLAN_CONTEXT=$(tail -c 4000 "$TRANSCRIPT_PATH" 2>/dev/null)
47
-
48
- if [ -z "$PLAN_CONTEXT" ]; then
49
- echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
50
- exit 0
51
- fi
52
-
53
- # Build the review prompt
54
- REVIEW_PROMPT="You are reviewing a plan that Claude Code generated. Analyze it for:
55
- - Missing steps or edge cases
56
- - Better approaches or optimizations
57
- - Potential bugs or issues
58
- - Security concerns
59
-
60
- Here is the plan context (from the conversation transcript):
61
-
62
- $PLAN_CONTEXT
63
-
64
- Provide concise, actionable feedback. Focus only on things that should change."
65
-
66
- # Run the blend (progress shows on stderr, results on stdout)
67
- BLEND_RESULTS=$(echo "$REVIEW_PROMPT" | node "$SCRIPT_DIR/dist/blend-cli.js" 2>/dev/stderr)
68
-
69
- if [ -z "$BLEND_RESULTS" ]; then
70
- echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
71
- exit 0
72
- fi
73
-
74
- # Build the additionalContext string
75
- CONTEXT="🧃 Smoothie auto-blend results — multiple models reviewed this plan:
76
-
77
- $BLEND_RESULTS
78
-
79
- IMPORTANT — You must do TWO things:
80
-
81
- 1. FIRST, show a brief Smoothie summary to the user so they know the blend ran. Format it like:
82
-
83
- 🧃 Smoothie reviewed this plan (N models):
84
- ModelName — one-line summary of their key feedback
85
- ModelName — one-line summary of their key feedback
86
- (skip models that returned errors or empty responses)
87
-
88
- 2. THEN revise the plan incorporating valid suggestions. Discard irrelevant ones."
89
-
90
- # Return: allow ExitPlanMode but inject blend results
91
- node -e "
92
- const ctx = $(echo "$CONTEXT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null);
93
- console.log(JSON.stringify({
94
- hookSpecificOutput: {
95
- hookEventName: 'PreToolUse',
96
- permissionDecision: 'allow',
97
- permissionDecisionReason: 'Smoothie auto-blend completed',
98
- additionalContext: ctx
99
- }
100
- }));
101
- "
102
-
103
- exit 0